Files
narya/src/java/com/threerings/nodemap/NodeMap.java
T
Michael Bayne a9720fd905 LGPLed the library. Hello world.
git-svn-id: svn+ssh://src.earth.threerings.net/narya/trunk@3099 542714f4-19e9-0310-aa3c-eee0fc999fb1
2004-08-27 02:21:06 +00:00

408 lines
9.9 KiB
Java

//
// $Id: NodeMap.java,v 1.10 2004/08/27 02:20:11 mdb Exp $
//
// Narya library - tools for developing networked games
// Copyright (C) 2002-2004 Three Rings Design, Inc., All Rights Reserved
// http://www.threerings.net/code/narya/
//
// 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
package com.threerings.nodemap;
import java.awt.*;
import java.util.*;
import com.samskivert.swing.ToolTipManager;
import com.samskivert.swing.ToolTipProvider;
import com.samskivert.swing.util.SwingUtil;
/**
* The node map class represents a directed graph of nodes comprised
* of {@link Node} objects. A user-specifiable {@link LayoutManager}
* is used to lay out the nodes in the graph.
*/
public class NodeMap
{
/**
* Construct a node map object with the given layout manager.
*
* @param lgmr the layout manager.
*/
public NodeMap (LayoutManager lmgr)
{
_lmgr = lmgr;
_nodes = new ArrayList();
_dirty = false;
}
/**
* Returns an iterator over the nodes in this node map.
*/
public Iterator nodes ()
{
return _nodes.iterator();
}
/**
* Force the nodes in the map to be re-laid out.
*/
public void layout ()
{
// do nothing if we have no nodes
if (_nodes.size() == 0) {
return;
}
// default to using the first node as the root
if (_root == null) {
_root = (Node)_nodes.get(0);
}
// lay out the nodes
_lmgr.layout(_root, _nodes);
// update bounding node positions
updateBounds();
// note that we're no longer dirty
_dirty = false;
}
/**
* Returns the coordinates of the supplied node, adjusted into
* absolute coordinates (where 0,0 is the upper left of the node map).
*/
public Point getNodeCoords (Node node)
{
return new Point(node.getX()-_minx, node.getY()-_miny);
}
/**
* Calculate the bounding rectangle that wholly contains the node
* map.
*/
protected void updateBounds ()
{
Iterator iter = _nodes.iterator();
while (iter.hasNext()) {
Node n = (Node)iter.next();
int x = n.getX(), y = n.getY();
int wid = n.getWidth(), hei = n.getHeight();
_minx = Math.min(_minx, x);
_maxx = Math.max(_maxx, x + wid);
_miny = Math.min(_miny, y);
_maxy = Math.max(_maxy, y + hei);
}
}
/**
* Render the node map to the given graphics context.
*
* @param g the graphics context.
*/
public void paint (Graphics g)
{
// lay out the nodes if necessary
if (_dirty) {
layout();
}
// translate context to node map origin
g.translate(-_minx, -_miny);
// render all node edges
int size = _nodes.size();
for (int ii = 0; ii < size; ii++) {
Node n = (Node)_nodes.get(ii);
n.paintEdges(g);
}
// render all nodes
for (int ii = 0; ii < size; ii++) {
Node n = (Node)_nodes.get(ii);
n.paint(g);
}
// highlight the last node entered
// if (_lastnode != null) {
// g.setColor(Color.red);
// int x = _lastnode.getX(), y = _lastnode.getY();
// int wid = _lastnode.getWidth(), hei = _lastnode.getHeight();
// g.drawRect(x, y, wid, hei);
// }
// draw the tool tip, if any
if (_tipper != null) {
// determine proper tool tip placement
Dimension tsize = _tipper.getToolTipSize(g);
Point pos = SwingUtil.fitRectInRect(
new Rectangle(_tipx, _tipy, tsize.width, tsize.height),
getBounds());
// paint away
_tipper.paintToolTip(g, pos.x, pos.y);
}
// restore original origin
g.translate(_minx, _miny);
}
/**
* Add the given node to the node map. A given node may not be
* present in the node map more than once.
*
* @param n the node to add.
*
* @exception DuplicateNodeException thrown if the node already
* exists in the node map.
*/
public void addNode (Node n) throws DuplicateNodeException
{
// don't allow adding a node more than once
if (_nodes.contains(n)) {
throw new DuplicateNodeException();
}
// add the node
_nodes.add(n);
_dirty = true;
}
/**
* Add the given edge to the node. Edges may not be added to a
* node more than once, or to a node that is not present in the
* node map.
*
* @param n the node.
* @param e the edge to add.
*
* @exception NoSuchNodeException thrown if the given node does
* not exist in the node map.
* @exception DuplicateEdgeException thrown if the given node
* already contains the given edge.
*/
public void addEdge (Node n, Edge e)
throws NoSuchNodeException, DuplicateEdgeException
{
// don't allow adding an edge to an unknown node
if (!_nodes.contains(n)) {
throw new NoSuchNodeException();
}
// add the edge to the node
n.addEdge(e);
_dirty = true;
}
/**
* Set the root node for the node map. The given node must be
* present in the node map.
*
* @param n the root node.
*
* @exception NoSuchNodeException thrown if the given node does
* not exist in the node map.
*/
public void setRootNode (Node n) throws NoSuchNodeException
{
if (!_nodes.contains(n)) {
throw new NoSuchNodeException();
}
_root = n;
_dirty = true;
}
/**
* Returns the dimensions of the node map in pixels.
*/
public Dimension getSize ()
{
// lay out the nodes if necessary
if (_dirty) {
layout();
}
return new Dimension(_maxx - _minx, _maxy - _miny);
}
/**
* Returns the bounding rectangle of the node map.
*/
public Rectangle getBounds ()
{
Dimension size = getSize();
return new Rectangle(_minx, _miny, size.width, size.height);
}
/**
* Handle mouse-moved events passed on by the containing panel.
* Inform any affected nodes of mouse-entered and mouse-exited
* events.
*
* @param x the mouse x-coordinate.
* @param y the mouse y-coordinate.
*/
public void handleMouseMoved (int x, int y)
{
Node enternode = null;
// translate coordinates to node map origin
x += _minx;
y += _miny;
// tell the tip manager that the mouse moved
if (_tipmgr != null) {
_tipmgr.handleMouseMoved(x, y);
}
// check whether we've entered a node
int size = _nodes.size();
for (int ii = 0; ii < size; ii++) {
Node n = (Node)_nodes.get(ii);
if (n.contains(x, y)) {
// bail if we're in the same node we were in last
if (_lastnode == n) return;
// note that we're entering this node
enternode = n;
break;
}
}
// exit the last node, if any
if (_lastnode != null) {
_lastnode.handleMouseExited();
if (_tipmgr != null) {
_tipmgr.handleMouseExited();
}
}
// enter the new node, if any
if (enternode != null) {
enternode.handleMouseEntered();
if (_tipmgr != null) {
_tipmgr.handleMouseEntered(enternode, x, y);
}
}
// update the last node entered
_lastnode = enternode;
}
/**
* Handle mouse-clicked events passed on by the containing panel.
* Inform any affected nodes of mouse-clicked events.
*
* @param x the mouse x-coordinate.
* @param y the mouse y-coordinate.
*/
public void handleMouseClicked (int x, int y)
{
// translate coordinates to node map origin
x += _minx;
y += _miny;
int size = _nodes.size();
for (int ii = 0; ii < size; ii++) {
Node n = (Node)_nodes.get(ii);
if (n.contains(x, y)) {
// inform the node of the mouse click, with
// coordinates translated to suit node interior
int nx = n.getX(), ny = n.getY();
n.handleMouseClicked(x - nx, y - ny);
// let the tool tip manager know about the click
if (_tipmgr != null) {
_tipmgr.handleMouseClicked(n);
}
// nodes can't overlap for now, so we're done
return;
}
}
}
/**
* Handle mouse-exited events passed on by the containing panel.
* Inform the tool tip manager that we've exited any node we may
* have been within.
*/
public void handleMouseExited ()
{
handleMouseMoved(-1, -1);
}
/**
* Set the tool tip provider for display when the node map is
* rendered.
*
* @param tipper the tool tip provider
* @param x the last mouse x-position.
* @param y the last mouse y-position.
*/
public void setToolTipProvider (ToolTipProvider tipper, int x, int y)
{
_tipper = tipper;
_tipx = x;
_tipy = y;
}
/**
* Set the tool tip manager that the node map should notify of
* node enter, exit, and clicked events.
*
* @param tipmgr the tool tip manager.
*/
public void setToolTipManager (ToolTipManager tipmgr)
{
_tipmgr = tipmgr;
}
/** The current tool tip provider. */
protected ToolTipProvider _tipper;
/** The mouse position for calculating tool tip display position. */
protected int _tipx, _tipy;
/** The tool tip manager. */
protected ToolTipManager _tipmgr;
/** The layout manager. */
protected LayoutManager _lmgr;
/** The nodes in the node map. */
protected ArrayList _nodes;
/** The root node. */
protected Node _root;
/** The bounding box coordinates. */
protected int _minx, _maxx, _miny, _maxy;
/** The last node the mouse entered. */
protected Node _lastnode;
/** Whether the node map needs to be laid out. */
protected boolean _dirty;
}