/*
* Copyright (C) 2002-@year@ by University of Maryland, College Park, MD 20742, USA
* All rights reserved.
*
* Piccolo was written at the Human-Computer Interaction Laboratory
* www.cs.umd.edu/hcil by Jesse Grosjean under the supervision of Ben Bederson.
* The Piccolo website is www.cs.umd.edu/hcil/piccolo
*/
package edu.umd.cs.piccolox.swing;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
import java.util.List;
import javax.swing.JScrollPane;
import edu.umd.cs.piccolo.PCamera;
import edu.umd.cs.piccolo.PCanvas;
import edu.umd.cs.piccolo.PLayer;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.PRoot;
import edu.umd.cs.piccolo.util.PAffineTransform;
import edu.umd.cs.piccolo.util.PBounds;
/**
* The default scroll director implementation. This default implementation
* follows the widely accepted model of scrolling - namely the scrollbars
* control the movement of the window over the document rather than the movement
* of the document under the window.
*
* @author Lance Good
*/
public class PDefaultScrollDirector implements PScrollDirector, PropertyChangeListener {
/**
* The viewport that signals this scroll director
*/
protected PViewport viewPort;
/**
* The scrollpane that contains the viewport
*/
protected PScrollPane scrollPane;
/**
* The canvas that this class directs
*/
protected PCanvas view;
/**
* The canvas' camera
*/
protected PCamera camera;
/**
* The canvas' root
*/
protected PRoot root;
/**
* Flag to indicate when scrolling is currently in progress
*/
protected boolean scrollInProgress = false;
/**
* The default constructor
*/
public PDefaultScrollDirector() {
}
/**
* Installs the scroll director and adds the appropriate listeners
* @param viewPort The viewport on which this director directs
* @param view The ZCanvas that the viewport looks at
*/
public void install(PViewport viewPort, final PCanvas view) {
this.scrollPane = (PScrollPane) viewPort.getParent();
this.viewPort = viewPort;
this.view = view;
if (view != null) {
this.camera = view.getCamera();
this.root = view.getRoot();
}
if (camera != null) {
camera.addPropertyChangeListener(this);
}
if (root != null) {
root.addPropertyChangeListener(this);
}
if (scrollPane != null) {
scrollPane.revalidate();
}
}
/**
* Uninstall the scroll director from the viewport
*/
public void unInstall() {
viewPort = null;
view = null;
if (camera != null) {
camera.removePropertyChangeListener(this);
}
if (root != null) {
root.removePropertyChangeListener(this);
}
camera = null;
root = null;
}
/**
* Get the View position given the specified camera bounds
* @param viewBounds The bounds for which the view position will be computed
* @return The view position
*/
public Point getViewPosition(Rectangle2D viewBounds) {
Point pos = new Point();
if (camera != null) {
// First we compute the union of all the layers
PBounds layerBounds = new PBounds();
List layers = camera.getLayersReference();
for(Iterator i=layers.iterator(); i.hasNext();) {
PLayer layer = (PLayer)i.next();
layerBounds.add(layer.getFullBoundsReference());
}
// Then we put the bounds into camera coordinates and
// union the camera bounds
camera.viewToLocal(layerBounds);
layerBounds.add(viewBounds);
pos.setLocation((int) (viewBounds.getX() - layerBounds.getX() + 0.5), (int) (viewBounds.getY() - layerBounds.getY() + 0.5));
}
return pos;
}
/**
* Get the size of the view based on the specified camera bounds
* @param viewBounds The view bounds for which the view size will be computed
* @return The view size
*/
public Dimension getViewSize(Rectangle2D viewBounds) {
Dimension size = new Dimension();
if (camera != null) {
// First we compute the union of all the layers
PBounds bounds = new PBounds();
List layers = camera.getLayersReference();
for(Iterator i=layers.iterator(); i.hasNext();) {
PLayer layer = (PLayer)i.next();
bounds.add(layer.getFullBoundsReference());
}
// Then we put the bounds into camera coordinates and
// union the camera bounds
if (!bounds.isEmpty()) {
camera.viewToLocal(bounds);
}
bounds.add(viewBounds);
size.setSize((int) (bounds.getWidth() + 0.5), (int) (bounds.getHeight() + 0.5));
}
return size;
}
/**
* Set the view position in a manner consistent with standardized scrolling
* @param x The new x position
* @param y The new y position
*/
public void setViewPosition(double x, double y) {
if (camera != null) {
// If a scroll is in progress - we ignore new scrolls -
// if we didn't, since the scrollbars depend on the camera location
// we can end up with an infinite loop
if (!scrollInProgress) {
scrollInProgress = true;
// Get the union of all the layers' bounds
PBounds layerBounds = new PBounds();
List layers = camera.getLayersReference();
for(Iterator i=layers.iterator(); i.hasNext();) {
PLayer layer = (PLayer)i.next();
layerBounds.add(layer.getFullBoundsReference());
}
PAffineTransform at = camera.getViewTransform();
at.transform(layerBounds,layerBounds);
// Union the camera bounds
PBounds viewBounds = camera.getBoundsReference();
layerBounds.add(viewBounds);
// Now find the new view position in view coordinates
Point2D newPoint = new Point2D.Double(layerBounds.getX() + x, layerBounds.getY() + y);
// Now transform the new view position into global coords
camera.localToView(newPoint);
// Compute the new matrix values to put the camera at the
// correct location
double newX = - (at.getScaleX() * newPoint.getX() + at.getShearX() * newPoint.getY());
double newY = - (at.getShearY() * newPoint.getX() + at.getScaleY() * newPoint.getY());
at.setTransform(at.getScaleX(), at.getShearY(), at.getShearX(), at.getScaleY(), newX, newY);
// Now actually set the camera's transform
camera.setViewTransform(at);
scrollInProgress = false;
}
}
}
/**
* Invoked when the camera's view changes, or the bounds of the root or camera changes
*/
public void propertyChange(PropertyChangeEvent pce) {
boolean isRelevantViewEvent = (PCamera.PROPERTY_VIEW_TRANSFORM == pce.getPropertyName());
boolean isRelevantBoundsEvent = (PNode.PROPERTY_BOUNDS == pce.getPropertyName() || PNode.PROPERTY_FULL_BOUNDS == pce.getPropertyName()) && (pce.getSource() == camera || pce.getSource() == view.getRoot());
if (isRelevantViewEvent || isRelevantBoundsEvent) {
if (shouldRevalidateScrollPane()) {
scrollPane.revalidate();
}
else {
viewPort.fireStateChanged();
}
}
}
/**
* Should the ScrollPane be revalidated. This occurs when either the
* scrollbars are showing and should be remove or are not showing and should
* be added.
*
* @return Whether the scroll pane should be revalidated
*/
public boolean shouldRevalidateScrollPane() {
if (camera != null) {
if (scrollPane.getHorizontalScrollBarPolicy() != JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED
&& scrollPane.getVerticalScrollBarPolicy() != JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED) {
return false;
}
// Get the union of all the layers' bounds
PBounds layerBounds = new PBounds();
List layers = camera.getLayersReference();
for(Iterator i=layers.iterator(); i.hasNext();) {
PLayer layer = (PLayer)i.next();
layerBounds.add(layer.getFullBoundsReference());
}
// Put into camera coordinates
camera.viewToLocal(layerBounds);
// And union with the camera bounds
PBounds cameraBounds = camera.getBoundsReference();
layerBounds.add(cameraBounds);
// Truncate these to ints before comparing since
// that's what the ScrollPane uses
int layerWidth = (int) (layerBounds.getWidth() + 0.5);
int layerHeight = (int) (layerBounds.getHeight() + 0.5);
int cameraWidth = (int) (cameraBounds.getWidth() + 0.5);
int cameraHeight = (int) (cameraBounds.getHeight() + 0.5);
if ((scrollPane.getHorizontalScrollBar().isShowing() && layerWidth <= cameraWidth)
|| (!scrollPane.getHorizontalScrollBar().isShowing() && layerWidth > cameraWidth)
|| (scrollPane.getVerticalScrollBar().isShowing() && layerHeight <= cameraHeight)
|| (!scrollPane.getVerticalScrollBar().isShowing() && layerHeight > cameraHeight)) {
return true;
}
}
return false;
}
}