/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University of
* California All rights reserved.
*
* This software is open-source under the BSD license; see either "license.txt"
* or http://jung.sourceforge.net/license.txt for a description.
* Created on Feb 2, 2005
*
*/
package edu.uci.ics.jung.visualization;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.Set;
import javax.swing.BoundedRangeModel;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import edu.uci.ics.jung.visualization.transform.BidirectionalTransformer;
import edu.uci.ics.jung.visualization.transform.shape.Intersector;
/**
* GraphZoomScrollPane is a Container for the Graph's VisualizationViewer
* and includes custom horizontal and vertical scrollbars.
* GraphZoomScrollPane listens for changes in the scale and
* translation of the VisualizationViewer, and will update the
* scrollbar positions and sizes accordingly. Changes in the
* scrollbar positions will cause the corresponding change in
* the translation component (offset) of the VisualizationViewer.
* The scrollbars are modified so that they will allow panning
* of the graph when the scale has been changed (e.g. zoomed-in
* or zoomed-out).
*
* The lower-right corner of this component is available to
* use as a small button or menu.
*
* samples.graph.GraphZoomScrollPaneDemo shows the use of this component.
*
* @author Tom Nelson
*
*
*/
@SuppressWarnings("serial")
public class GraphZoomScrollPane extends JPanel {
protected VisualizationViewer vv;
protected JScrollBar horizontalScrollBar;
protected JScrollBar verticalScrollBar;
protected JComponent corner;
protected boolean scrollBarsMayControlAdjusting = true;
protected JPanel south;
/**
* Create an instance of the GraphZoomScrollPane to contain the
* VisualizationViewer
* @param vv
*/
public GraphZoomScrollPane(VisualizationViewer vv) {
super(new BorderLayout());
this.vv = vv;
addComponentListener(new ResizeListener());
Dimension d = vv.getGraphLayout().getSize();
verticalScrollBar = new JScrollBar(JScrollBar.VERTICAL, 0, d.height, 0, d.height);
horizontalScrollBar = new JScrollBar(JScrollBar.HORIZONTAL, 0, d.width, 0, d.width);
verticalScrollBar.addAdjustmentListener(new VerticalAdjustmentListenerImpl());
horizontalScrollBar.addAdjustmentListener(new HorizontalAdjustmentListenerImpl());
verticalScrollBar.setUnitIncrement(20);
horizontalScrollBar.setUnitIncrement(20);
// respond to changes in the VisualizationViewer's transform
// and set the scroll bar parameters appropriately
vv.addChangeListener(
new ChangeListener(){
public void stateChanged(ChangeEvent evt) {
VisualizationViewer vv =
(VisualizationViewer)evt.getSource();
setScrollBars(vv);
}
});
add(vv);
add(verticalScrollBar, BorderLayout.EAST);
south = new JPanel(new BorderLayout());
south.add(horizontalScrollBar);
setCorner(new JPanel());
add(south, BorderLayout.SOUTH);
}
/**
* listener for adjustment of the horizontal scroll bar.
* Sets the translation of the VisualizationViewer
*/
class HorizontalAdjustmentListenerImpl implements AdjustmentListener {
int previous = 0;
public void adjustmentValueChanged(final AdjustmentEvent e) {
// LCMC fix
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
int hval = e.getValue();
float dh = previous - hval;
previous = hval;
if(dh != 0 && scrollBarsMayControlAdjusting) {
// get the uniform scale of all transforms
float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
dh *= layoutScale;
AffineTransform at = AffineTransform.getTranslateInstance(dh, 0);
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at);
}
}
});
}
}
/**
* Listener for adjustment of the vertical scroll bar.
* Sets the translation of the VisualizationViewer
*/
class VerticalAdjustmentListenerImpl implements AdjustmentListener {
int previous = 0;
public void adjustmentValueChanged(final AdjustmentEvent e) {
// LCMC fix
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JScrollBar sb = (JScrollBar)e.getSource();
BoundedRangeModel m = sb.getModel();
int vval = m.getValue();
float dv = previous - vval;
previous = vval;
if(dv != 0 && scrollBarsMayControlAdjusting) {
// get the uniform scale of all transforms
float layoutScale = (float) vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getScale();
dv *= layoutScale;
AffineTransform at = AffineTransform.getTranslateInstance(0, dv);
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).preConcatenate(at);
}
}
});
}
}
/**
* use the supplied vv characteristics to set the position and
* dimensions of the scroll bars. Called in response to
* a ChangeEvent from the VisualizationViewer
* @param xform the transform of the VisualizationViewer
*/
private void setScrollBars(VisualizationViewer vv) {
Dimension d = vv.getGraphLayout().getSize();
Rectangle2D vvBounds = vv.getBounds();
// a rectangle representing the layout
Rectangle layoutRectangle =
new Rectangle(0,0,d.width,d.height);
//-d.width/2, -d.height/2, 2*d.width, 2*d.height);
BidirectionalTransformer viewTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW);
BidirectionalTransformer layoutTransformer = vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT);
Point2D h0 = new Point2D.Double(vvBounds.getMinX(), vvBounds.getCenterY());
Point2D h1 = new Point2D.Double(vvBounds.getMaxX(), vvBounds.getCenterY());
Point2D v0 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMinY());
Point2D v1 = new Point2D.Double(vvBounds.getCenterX(), vvBounds.getMaxY());
h0 = viewTransformer.inverseTransform(h0);
h0 = layoutTransformer.inverseTransform(h0);
h1 = viewTransformer.inverseTransform(h1);
h1 = layoutTransformer.inverseTransform(h1);
v0 = viewTransformer.inverseTransform(v0);
v0 = layoutTransformer.inverseTransform(v0);
v1 = viewTransformer.inverseTransform(v1);
v1 = layoutTransformer.inverseTransform(v1);
scrollBarsMayControlAdjusting = false;
setScrollBarValues(layoutRectangle, h0, h1, v0, v1);
scrollBarsMayControlAdjusting = true;
}
@SuppressWarnings("unchecked")
protected void setScrollBarValues(Rectangle rectangle,
Point2D h0, Point2D h1,
Point2D v0, Point2D v1) {
boolean containsH0 = rectangle.contains(h0);
boolean containsH1 = rectangle.contains(h1);
boolean containsV0 = rectangle.contains(v0);
boolean containsV1 = rectangle.contains(v1);
// horizontal scrollbar:
Intersector intersector = new Intersector(rectangle, new Line2D.Double(h0, h1));
int min = 0;
int ext;
int val = 0;
int max;
Set points = intersector.getPoints();
Point2D first = null;
Point2D second = null;
Point2D[] pointArray = (Point2D[])points.toArray(new Point2D[points.size()]);
if(pointArray.length > 1) {
first = pointArray[0];
second = pointArray[1];
} else if(pointArray.length > 0) {
first = second = pointArray[0];
}
if(first != null && second != null) {
// correct direction of intersect points
if((h0.getX() - h1.getX()) * (first.getX() - second.getX()) < 0) {
// swap them
Point2D temp = first;
first = second;
second = temp;
}
if(containsH0 && containsH1) {
max = (int)first.distance(second);
val = (int)first.distance(h0);
ext = (int)h0.distance(h1);
} else if(containsH0) {
max = (int)first.distance(second);
val = (int)first.distance(h0);
ext = (int)h0.distance(second);
} else if(containsH1) {
max = (int) first.distance(second);
val = 0;
ext = (int) first.distance(h1);
} else {
max = ext = rectangle.width;
val = min;
}
horizontalScrollBar.setValues(val, ext+1, min, max);
}
// vertical scroll bar
min = val = 0;
intersector.intersectLine(new Line2D.Double(v0, v1));
points = intersector.getPoints();
pointArray = (Point2D[])points.toArray(new Point2D[points.size()]);
if(pointArray.length > 1) {
first = pointArray[0];
second = pointArray[1];
} else if(pointArray.length > 0) {
first = second = pointArray[0];
}
if(first != null && second != null) {
// arrange for direction
if((v0.getY() - v1.getY()) * (first.getY() - second.getY()) < 0) {
// swap them
Point2D temp = first;
first = second;
second = temp;
}
if(containsV0 && containsV1) {
max = (int)first.distance(second);
val = (int)first.distance(v0);
ext = (int)v0.distance(v1);
} else if(containsV0) {
max = (int)first.distance(second);
val = (int)first.distance(v0);
ext = (int)v0.distance(second);
} else if(containsV1) {
max = (int) first.distance(second);
val = 0;
ext = (int) first.distance(v1);
} else {
max = ext = rectangle.height;
val = min;
}
verticalScrollBar.setValues(val, ext+1, min, max);
}
}
/**
* Listener to adjust the scroll bar parameters when the window
* is resized
*/
protected class ResizeListener extends ComponentAdapter {
public void componentHidden(ComponentEvent e) {
}
public void componentResized(ComponentEvent e) {
setScrollBars(vv);
}
public void componentShown(ComponentEvent e) {
}
}
/**
* @return Returns the corner component.
*/
public JComponent getCorner() {
return corner;
}
/**
* @param corner The cornerButton to set.
*/
public void setCorner(JComponent corner) {
this.corner = corner;
corner.setPreferredSize(new Dimension(verticalScrollBar.getPreferredSize().width,
horizontalScrollBar.getPreferredSize().height));
south.add(this.corner, BorderLayout.EAST);
}
public JScrollBar getHorizontalScrollBar() {
return horizontalScrollBar;
}
public JScrollBar getVerticalScrollBar() {
return verticalScrollBar;
}
}