/*
* GeoTools java GIS tookit (c) The Centre for Computational Geography 2002
*
* 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 version 2.1
*/
package uk.ac.leeds.ccg.geotools;
import java.awt.*;
import java.util.*;
import uk.ac.leeds.ccg.widgets.*;
import uk.ac.leeds.ccg.geotools.projections.*;
import java.util.TreeMap;
/**
*
* Viewer is a medium for displaying geographic maps in applets and applications.
* The map to display is built up from information stored in a number of themes.
*
* To construct a working viewer you need to understand how to construct themes
* and layers; a task that will be made easier over time as more helper classes are
* written.
*
* A viewer has the ability to build a scaled image from up to three parts:
* 1)any number of static themes - not animated at all
* 2)a sequence of single static themes
* 3)animated themes
*
* Notes: use (add/remove)viewerClickedListener instead of viewerClickListener
* from now on. viewerClickListeners have now been removed!
*
*
* $Log: Viewer.java,v $
* Revision 1.39 2002/01/19 17:10:58 loxnard
* Fixed JavaDoc comments.
*
*
* @author James Macgill
* @author Mathieu Van Loon - setMapExtentByFactor, setMapExtentByValue
* @version $Revision: 1.39 $ $Date: 2002/01/19 17:10:58 $
* @since 0.6.4
*
*/
public class Viewer extends java.awt.Component implements ScaleChangedListener,ThemeChangedListener {
/**
* Version information.
*/
public static final String cvsid = "$Id: Viewer.java,v 1.39 2002/01/19 17:10:58 loxnard Exp $";
/**
* Constant that represents the zoom mode for a viewer.
*/
public static final int ZOOM = 0;
/**
* Constant that represents the pan mode for a viewer.
*/
public static final int PAN = 1;
/**
* Constant that represents the navigate mode for a viewer.
*/
public static final int NAVIGATE = 2;
/**
* Constant that represents the selection mode for a viewer.
*/
public static final int SELECT=3;
/**
* Indicates preferred size of component.
*/
public Dimension pd = new Dimension(50,50);
/**
* Indicates maximum size of component.
*/
public Dimension maxd;
/**
* Indicates minimum size of component.
*/
public Dimension mind= new Dimension(50,50);
/**
* The current tool mode for the viewer.
*/
//private int toolMode = ZOOM;
/**
* The current tool.
*/
private Tool tool;
private Graphics toolGraphics;
/**
* Scaler responsible for converting real-world values to scale.
*/
public Scaler scale;
/**
* Keep count of how many themes we are displaying.
*/
private int themeCount = 0;
private final boolean debug=false;
String name="Un-named Viewer ";
/**
* Used to store an image of the completed scaled map sections.
* Buffer holds full image before display.
* Static holds the static unchanging themes.
* Sequence is for an animated section.
* Pan is for smooth panning of the image.
* Selection is experimental but is for speeding up selections.
*/
private transient Image screenBuffer,staticBuffer,sequenceBuffer[],panBuffer,selectionBuffer;
/**
* A vector containing all the static themes in this viewer.
*/
private Vector staticThemes = new Vector();
/**
* A vector containing all the static themes to draw.
*/
private Vector visibleThemes = new Vector();
/**
* An array of themes that make up the sequence part of the display.
*/
private Theme sequenceTheme[],animationTheme;
/**
* A record of the last known size of this component.
* Used to check for changes in the dimensions of the component.
*/
//private Rectangle last_size;
/**
* Used to track the current status of the mouse.
* Contains info on the current mouse position (in screen, projection and geographic space)
* as well as details of the last drag operation (start and end point in all three systems).
*/
private MouseStatus mouseStatus;
/**
* geoRectangle bounding box that will hold all layers
*/
private GeoRectangle fullMapExtent = new GeoRectangle();
/**
* Notes whether the backdrop has been set up yet
*/
private boolean displaying = false;
/**
* Stores the current frame number for the sequence buffer
*/
private int frame = 0;
/**
* highlighting modes
* INSTANT = As mouse moves around screen
* now on REQUEST by default;
*/
public final static int INSTANT = 0;
/**
* highlighting modes
* REQUEST = As mouse clicks on object
* now on REQUEST by default;
*/
public final static int REQUEST = 1;
/**
* Holds the highlighting mode
* INSTANT = As mouse moves around screen
* REQUEST = As mouse clicks on object
* now on INSTANT by default;
*/
private int highlightMode = INSTANT;
/**
* Holds the ID of the object currently under the pointer
*/
private int currentID = 0;
/**
* Holds all of the listeners for ID changed events
*/
private Vector listeners = new Vector();
/**
* Holds all of the click Listeners
*/
private Vector clickedListeners = new Vector();
/**
*Holds the composition Listeners
*/
private Vector compositionChangedListeners = new Vector();
/**
* Holds all of the highlight position change listeners
*/
private Vector hpcListeners = new Vector();
/**
* Holds all of the selection position change listeners
*/
private Vector spcListeners = new Vector();
/**
* Holds all of the selection region change listeners
*/
private Vector srcListeners = new Vector();
private SelectionRegionChangedListener srcFinal = null;
/**
* Holds a flag for each frame in the sequence buffer to say
* if it is up-to-date or not
*/
private boolean sequenceFrameValid[];
/**
* Notes if the selection has changed since the last repaint
*/
private boolean selectionChanged = true;
/**
* Holds the last known size of the viewer
* as a check for when it changes
*/
private Rectangle lastSize = new Rectangle();
private boolean showTips = true;
//private MouseIdle mouseIdle;
//private boolean mouseStill = true;
/**
* The time in milliseconds that the mouse must be still for before a tool tip is displayed.
*/
protected int timeout = 250;
/**
* ThemeStack to hold the themes for this viewer.
*/
protected ThemeStack themeStack;
/** Default constructor for a viewer.
* Sets up and initialises a new viewer. It won't do much, however,
* until it has at least one theme added to it.
*
* @see uk.ac.leeds.ccg.geotools.Viewer#addTheme
* @see uk.ac.leeds.ccg.geotools.Viewer#addStaticTheme
* @see uk.ac.leeds.ccg.geotools.Viewer#addSequenceTheme
*/
public Viewer() {
this(true);
}
/**
*
* Default constructor for a viewer.
* Sets up and initialises a new viewer. It won't do much, however,
* until it has at least one theme added to it.
* @param activeMouse If set to false then this viewer will not respond to any mouse events.
* @see uk.ac.leeds.ccg.geotools.Viewer#addTheme
* @see uk.ac.leeds.ccg.geotools.Viewer#addStaticTheme
* @see uk.ac.leeds.ccg.geotools.Viewer#addSequenceTheme
*/
public Viewer(boolean activeMouse) {
themeStack = new ThemeStack(this);
if(debug)System.out.println("---->uk.ac.leeds.ccg.geotools.Viewer constructed, will identify itself as V--->");
if(debug)System.out.println("V--->Viewer Started");
//last_size = new Rectangle();
//last_size.add(this.getBounds());
mouseStatus = new MouseStatus(this);
setTool(new ZoomTool());
scale = new Scaler(fullMapExtent,this.getBounds());
if(activeMouse){
ViewerMouse aViewerMouse = new ViewerMouse();
this.addMouseListener(aViewerMouse);
ViewerMouseMotion aViewerMouseMotion = new ViewerMouseMotion();
this.addMouseMotionListener(aViewerMouseMotion);
ComponentAdapt adapt = new ComponentAdapt();
this.addComponentListener(adapt);
}
}
/** Add a new theme.
* There is some duplication here as addTheme simply calls addStaticTheme.
* @param t Theme to be added.
* @param waight Position the theme should take within the stack.
*/
public void addTheme(Theme t,int waight){
addStaticTheme(t,waight);
}
/**
* Add a new theme.
* There is some duplication here as addTheme simply calls addStaticTheme.
* @param t Theme to be added.
*/
public void addTheme(Theme t){
addStaticTheme(t,0);
}
/**
* Gets the preferred size of this component.
* @return Preferred size of component.
*/
public Dimension getPreferredSize(){
if(debug)System.out.println("V--->"+name+"Pref "+pd.width+" "+pd.height);
return pd;
}
/**
* Gets the minimum size of this component.
* @return Minimum size of component.
*/
public Dimension getMinimumSize(){
// System.out.println("V--->"+name+"Min "+mind.width+" "+mind.height);
if(debug)System.out.println("V--->Min size called");
return mind;
}
/**
* Gets the maximum size of this component.
* @return Maximum size of component.
*/
public Dimension getMaximumSize(){
if(debug)System.out.println("V--->"+name+"Max "+maxd.width+" "+maxd.height);
return maxd;
}
/**
* Sets the preferred size of this component.
* @param d Preferred size of component.
*/
public void setPreferredSize(Dimension d){
if(debug)System.out.println("V--->"+name+"Pref "+d.width+" "+d.height);
pd=d;
}
/**
* Sets the minimum size of this component.
* @param d Minimum size of component.
*/
public void setMinimumSize(Dimension d){
if(debug)System.out.println("V--->"+name+"Min "+d.width+" "+d.height);
mind=d;
}
/**
* Sets the maximum size of this component.
* @param d Maximum size of component.
*/
public void setMaximumSize(Dimension d){
if(debug)System.out.println("V--->"+name+"Max "+d.width+" "+d.height);
maxd=d;
}
/**
* Moves and resizes component.
* @param x The new X coordinate.
* @param y The new Y coordinate.
* @param w The new width.
* @param h The new height.
*/
public void setBounds(int x,int y,int w,int h){
super.setBounds(x,y,w,h);
pd=new Dimension(w,h);
mind=new Dimension(w,h);
maxd=new Dimension(w,h);
}
/**
* Removes a theme.
* @param t Theme to be removed.
*/
public void removeTheme(Theme t){
removeStaticTheme(t);
}
/**
* Removes a static theme.
* @param t Theme to be removed.
*/
public void removeStaticTheme(Theme t){
if(visibleThemes.contains(t)){visibleThemes.removeElement(t);}
if(staticThemes.contains(t)){
staticThemes.removeElement(t);
removeHighlightPositionChangedListener(t);
removeSelectionPositionChangedListener(t);
removeSelectionRegionChangedListener(t);
themeStack.removeTheme(t);
themeCount--;
updateStaticBuffer();
}
notifyCompositionChanged(CompositionChangedEvent.REMOVED);
}
/**
* Add an array of themes.
* These can then be stepped through in sequence
* or displayed on request.
* @param t Array of themes to be added.
*/
public void setSequenceTheme(Theme t[]){
if(sequenceTheme==null){themeCount++;}
createSequenceBuffer(t.length);
sequenceTheme = t;
//add all the themes to list of highlight position changed listeners
for(int i = 0;i"+name+"Adding a theme to Bounds\n"+
fullMapExtent);
fullMapExtent.add(t[i].getBounds());
if(debug)System.out.println("V--->"+name+"Added a theme to Bounds\n"+
fullMapExtent);
}
if(themeCount==1){
setupScale();
}
/*updateSequenceBuffer();*/
invalidateSequenceBuffer();
}
/**
* Selects which of the sequence frames to display.
* @param f SequenceFrame to display.
*/
public void setSequenceFrame(int f){
frame = f;
if(debug)System.out.println("V--->Frame set to"+f);
//map_point = new GeoPoint(map_xy[0],map_xy[1]);
sequenceTheme[frame].setHighlight(mouseStatus.map_point);
update(this.getGraphics());
}
/**
* Adds the theme that will be used to hold any animations.
* @param t Theme to hold animations.
*/
public void setAnimationTheme(Theme t){
if(animationTheme!=null){
animationTheme.removeThemeChangedListener(this);
}
animationTheme = t;
if(animationTheme!=null){
t.addThemeChangedListener(this);
}
}
/**
* Used internally to create the sequence buffer
*/
private void createSequenceBuffer(int size){
if(getBounds().width==0||this.createImage(getBounds().width,getBounds().height)==null){
//throw an exception ???
//throw(new Error("viewers MUST be added to a peer (like applet or frame etc. before they can be used)"));
return;
}
sequenceBuffer = new Image[size];
sequenceFrameValid = new boolean[size];
for(int i = 0;i
* viewer.REQUEST will only change the highlight when the user clicks the mouse.
*/
public void setHighlightMode(int flag){
highlightMode = flag;
}
/**
* Set the timeout before displaying a tooltip for a feature.
* @param t Timeout required before displaying tooltip in milliseconds.
*/
public void setToolTipTimeout(int t){
timeout = t;
}
/**
* Get the timeout before displaying a tooltip for a feature.
* @return Timeout required before displaying tooltip.
*/
public int getToolTipTimeout(){
return timeout;
}
/**
* Update the contents of the sequenceBuffer
* (may be processor intensive!!!)
*/
private void updateSequenceBuffer(){
Graphics g = null;
if(sequenceBuffer!=null){
for(int i = 0;i < sequenceBuffer.length;i++){
g = sequenceBuffer[i].getGraphics();
if(staticBuffer != null){
g.drawImage(staticBuffer,0,0,this);
}
sequenceTheme[i].paintScaled(g,scale);
sequenceFrameValid[i] = true;
//setSequenceFrame(i);//experimental, needs a switch as this may not always be desired
}
}
}
private void updateSequenceBuffer(int frame){
Graphics g = null;
if(sequenceBuffer!=null){
g = sequenceBuffer[frame].getGraphics();
if(staticBuffer != null){
g.drawImage(staticBuffer,0,0,this);
}
sequenceTheme[frame].paintScaled(g,scale);
sequenceFrameValid[frame] = true;
}
}
/**
* Clear the up-to-date flags for all of the sequence buffers.
*/
public void invalidateSequenceBuffer(){
if(sequenceBuffer!=null){
for(int i = 0;i < sequenceBuffer.length;i++){
sequenceFrameValid[i] = false;
}
}
}
/**
* Clear the up-to-date flags for all of the static buffer.
*/
public void invalidateStaticBuffer(){
updateStaticBuffer();
repaint();
notifyCompositionChanged(CompositionChangedEvent.VISIBILITY);
}
/**
* Add a new static theme.
* @param t Static theme to be added.
*/
public void addStaticTheme(Theme t){
addStaticTheme(t,0);
}
/**
* Add a new static theme.
* @param t Static theme to be added.
* @param waight Position theme should take within the stack of themes.
*/
public void addStaticTheme(Theme t,int waight){
themeStack.addTheme(t,waight,true);
staticThemes.addElement(t);
visibleThemes.addElement(t);
themeCount++;
//if no scale has been set then scale to this theme
if(t.getBounds().width>0){
if(debug)System.out.println("V--->"+name+"Adding a theme to Bounds\n"+
fullMapExtent);
fullMapExtent.add(t.getBounds());
if(debug)System.out.println("V--->"+name+"Added a theme to Bounds\n"+
fullMapExtent);
}
if(themeCount==1){ //is this the first?
setupScale();
}
//update the staticBuffer to acount for this addition
updateStaticBuffer();
//add theme to those notified when highlight position changes
addHighlightPositionChangedListener(t);
addSelectionPositionChangedListener(t);
addSelectionRegionChangedListener(t);
t.addThemeChangedListener(this);
notifyCompositionChanged(CompositionChangedEvent.ADDED);
// if(debug)System.out.println("New theme added");
}
private void setupScale(){
lastSize = getBounds();
if(scale!=null){
if(debug)System.out.println("V--->"+name+"Setting up a non null scaler");
scale.removeScaleChangedListener(this);
}
//scale = new Scaler(fullMapExtent,this.getBounds());
scale.addScaleChangedListener(this);
scale.setGraphicsExtent(getBounds());
scale.setMapExtent(fullMapExtent,true);
}
/**
* Called when something has happened that requires the staticBuffer
* to be redrawn.
* This may be a change in scale or an additional theme being added.
*/
private synchronized void updateStaticBuffer(){
Cursor old = this.getCursor();
if(debug)System.out.println("V--->"+name+"Updating Static Buffer");
selectionChanged = true;
this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
if(staticBuffer == null&&getBounds().width>0)
staticBuffer = this.createImage(getBounds().width,getBounds().height);
if(staticBuffer == null) {
this.setCursor(old);
return;
}
Graphics g = staticBuffer.getGraphics();
Theme t;
// if(debug)System.out.println("Updating static buffer");
g.setColor(this.getBackground());
Rectangle r=this.getBounds();
g.fillRect(0,0,r.width,r.height);
//for (Enumeration e = staticThemes.elements() ; e.hasMoreElements() ;) {
ThemeStack.ThemeInfo[] list = themeStack.getOrderedThemeInfos();
if(debug)System.out.println("V--->"+name+"Ordered list contains "+list.length+" entries");
for(int i=0;i"+name+"Painting theme in updatestatic buffer");
//if(visibleThemes.contains(t)){
if(debug)System.out.println("Drawing "+t);
t.paintScaled(g,scale);
//}
}
/*
for (Enumeration e = visibleThemes.elements() ; e.hasMoreElements() ;) {
t=(Theme)e.nextElement();
if(debug)System.out.println("V--->"+name+"Painting theme in updatestatic buffer");
//if(visibleThemes.contains(t)){
if(debug)System.out.println("Drawing "+t);
t.paintScaled(g,scale);
//}
}
*/
this.setCursor(old);
//force update of selection buffer to reflect changes in this buffer
//for now, setting flag, but could possibly call updateSelectionBuffer directly?
this.selectionChanged = true;
//repaint();
}
/**
* Called when something has happened that requires the staticBuffer
* to be redrawn.
* This may be a change in scale or an additional theme being added.
*/
private void updateSelectionBuffer(){
Cursor old = this.getCursor();
if(debug)System.out.println("V--->"+name+"Updating Selection Buffer");
selectionChanged = false;
//this.setCursor(new Cursor(Cursor.WAIT_CURSOR));
if(selectionBuffer == null&&getBounds().width>0)
//System.out.println("Creating static buffer");
selectionBuffer = this.createImage(getBounds().width,getBounds().height);
if(selectionBuffer == null) {
//System.out.println("Null static buffer");
return;
}
Graphics g = selectionBuffer.getGraphics();
Theme t;
//System.out.println("Painting selections");
g.drawImage(staticBuffer,0,0,this);
paintSelections(g);
}
private void paintHighlights(Graphics g){
Theme t;
for (Enumeration e = staticThemes.elements() ; e.hasMoreElements() ;) {
t=(Theme)e.nextElement();
if(isThemeVisible(t)){
t.paintHighlight(g,scale);
}
}
try{
if(sequenceBuffer[frame]!=null)
sequenceTheme[frame].paintHighlight(g,scale);//experiment
}catch(Exception e){}
}
private void paintSelections(Graphics g){
Theme t;
for (Enumeration e = staticThemes.elements() ; e.hasMoreElements() ;) {
if(debug){System.out.println("V--->painting themes selections");}
t=(Theme)e.nextElement();
t.paintSelection(g,scale);
}
try{
if(sequenceBuffer[frame]!=null)
sequenceTheme[frame].paintSelection(g,scale);//experiment
}catch(Exception e){}
if(debug){System.out.println("V--->Done");}
selectionChanged = false;
}
/**
* Gets the map extent that fits round all currently selected features.
* @return GeoRectangle representing the bounding box of the selected features.
*/
public GeoRectangle getSelectionMapExtent(){
GeoRectangle sme = new GeoRectangle();
Theme t;
for (Enumeration e = staticThemes.elements() ; e.hasMoreElements() ;) {
t=(Theme)e.nextElement();
sme.add(t.getSelectionMapExtent());
}
return sme;
}
/**
* Scales the map so that the given theme fills the viewer.
* @param t Theme that defines the new visible extents.
*/
public void setMapExtent(Theme t){
if(themeCount >0){
scale.setMapExtent(t.getBounds());
}else{
fullMapExtent.add(t.getBounds());
setupScale();
}
}
/** Sets the maximum map extent to use for resets and drawing
* until another theme is added.
* @param t Theme that defines the maximum map extents.
*/
public void setMaximumMapExtent(Theme t){
setMaximumMapExtent(t.getBounds());
}
/**
* Sets the maximum map extent to use for resets and drawing
* until another theme is added.
* @param r A Georectangle that defines the maximum visible extents.
*/
public void setMaximumMapExtent(GeoRectangle r){
fullMapExtent=r;
}
/**
* Scales the map so that the given point is at the centre of the view.
* @param p The point to centre on.
*/
public void centerOnPoint(GeoPoint p){
GeoRectangle bounds = scale.getMapExtent();
double xOffset = bounds.width/2;
double yOffset = bounds.height/2;
GeoRectangle newBounds = new GeoRectangle(p.x-xOffset,p.y-yOffset,xOffset*2,yOffset*2);
setMapExtent(newBounds);
}
/**
* Scales the map so that the given point is at the centre of the view and then zooms to the specified amount.
* @param p The point to centre on.
* @param percent The amount to zoom in by.
*/
public void zoomOnPoint(GeoPoint p,double percent){
GeoRectangle bounds = scale.getMapExtent();
double newWidth = fullMapExtent.width*(100d/percent);
double newHeight = fullMapExtent.height*(100d/percent);
double xOffset = newWidth/2d;
double yOffset = newHeight/2d;
GeoRectangle newBounds = new GeoRectangle(p.x-xOffset,p.y-yOffset,xOffset*2,yOffset*2);
if(debug)System.out.println("V--->old "+bounds+"\n new "+newBounds);
setMapExtent(newBounds);
}
/**
* Scales the map so that the current centre remains whilst the zoom level is set to a percentage of the full size.
* @param percent The amount to zoom in by. */
public void zoomPercent(double percent){
GeoRectangle bounds = scale.getMapExtent();
GeoPoint p = new GeoPoint(bounds.x+bounds.width/2,bounds.y+bounds.height/2);
double newWidth = fullMapExtent.width*(100d/percent);
double newHeight = fullMapExtent.height*(100d/percent);
double xOffset = newWidth/2d;
double yOffset = newHeight/2d;
GeoRectangle newBounds = new GeoRectangle(p.x-xOffset,p.y-yOffset,xOffset*2,yOffset*2);
if(debug)System.out.println("V--->old "+bounds+"\n new "+newBounds);
setMapExtent(newBounds);
}
/**
* Calculates the value of the current zoom level as a percentage of the full map size.
* @return The current zoom factor as a percentage.
*/
public double getZoomAsPercent(){
return Math.max((100d/scale.getMapExtent().getWidth())*fullMapExtent.width,(100d/scale.getMapExtent().getHeight())*fullMapExtent.height);
}
/**
* Scales the map so that the given point is at the centre of the view and then zooms out by the specified amount.
* @param p The point to centre on.
* @param percent The amount to zoom out by.
*/
public void zoomOutOnPoint(GeoPoint p,double percent){
double per = getZoomAsPercent();
if(debug)System.out.println("Zoom out from "+per);
per -= (percent/100d)*per;
if(debug)System.out.println("Zoom out to "+per);
zoomOnPoint(p,per);
}
/**
* Scales the map so that the given point is at the centre of the view and then zooms in by the specified amount.
* @param p The point to centre on.
* @param percent The amount to zoom in by.
*/
public void zoomInOnPoint(GeoPoint p,double percent){
double per = getZoomAsPercent();
if(debug)System.out.println("Zoom in from "+per);
per += (percent/100d)*per;
if(debug)System.out.println("Zoom in to "+per);
zoomOnPoint(p,per);
}
/**
* Scales the map so that the given rectangle fills the viewer.
* @param bounds A Georectangle that defines the new visible extents.
*/
public void setMapExtent(GeoRectangle bounds){
setMapExtent(bounds,false);
}
/**
* Scales the map so that the given rectangle fills the viewer.
* @param bounds A Georectangle that defines the new visible extents.
* @param quiet If quiet=true, then do not trigger a repaint.
*/
public void setMapExtent(GeoRectangle bounds,boolean quiet){
if(themeCount >0){
scale.setMapExtent(bounds,quiet);
// I commented out the following. When quiet if set=true, the
// viewer should NOT be updated. If you want the viewer updated,
// then set quiet=false.
// Cameron Shorter 29/10/1
//
// I think this is needed to actually change the viewer! (says who?)
//if(quiet)scaleChanged(new ScaleChangedEvent(scale,1.0));
}else{
fullMapExtent.add(bounds);
setupScale();
}
}
/**
* Set the map extent to fit in all layers.
*/
public void setMapExtentFull(){
setMapExtentFull(false);
}
/**
* Set the map extent to fit in all layers.
* @param quiet If quiet=true, then do not trigger a repaint.
*/
public void setMapExtentFull(boolean quiet){
if(themeCount >0){
setMapExtent(fullMapExtent,quiet);
}
}
/**
* Scales the map by the given zoomfactor. The lower bound
* of zooming is the full map containing all themes. There is
* no upper bound.
* This is probably deprecated and will be removed in the near future.
* @param zoomFactor A positive value means zoom out. A negative value means zoom in.
*/
public void setMapExtentByFactor(double zoomFactor) {
if(themeCount > 0 && zoomFactor != 1) {
GeoRectangle currentMap = scale.getMapExtent();
double newX, newY, newWidth, newHeight;
double tmp;
tmp = (currentMap.height*zoomFactor) - currentMap.height;
newHeight = currentMap.height+tmp;
newY = currentMap.y - (tmp/2);
tmp = (currentMap.width*zoomFactor) - currentMap.width;
newWidth = currentMap.width+tmp;
newX = currentMap.x - (tmp/2);
if(zoomFactor < 1 || fullMapExtent.contains(newX, newY, newWidth, newHeight)) {
scale.setMapExtent(new GeoRectangle(newX, newY, newWidth, newHeight));
} else {
scale.setMapExtent(fullMapExtent);
}
}
}
/**
* Specify an absolute scaleFactor. This method is likely to change
* in the near future.
* @param scaleFactor An absolute scalefactor.
*/
public void setMapExtentByValue(double scaleFactor) {
if(themeCount > 0) {
setMapExtentByFactor(scaleFactor/scale.getScaleFactor());
}
}
/**
* Set the map extent to fit all selected features.
*/
public void setMapExtentSelected(){
if(themeCount >0){
GeoRectangle r = getSelectionMapExtent();
if(r.getBounds().width>0){
scale.setMapExtent(r);
}
}
}
/**
* Set the scaler used to scale the map to and from the screen.
* Probably not a good idea to use this unless you know what you are doing.
* The setMapExtent is probably what you are after.
* @param scale_ A new scaler object to be used by this viewer.
*/
public void setScale(Scaler scale_) {
scale.removeScaleChangedListener(this);
this.scale = scale_;
scale.addScaleChangedListener(this);
}
/** Sets the geographic projection system for the viewer to reproject into on the fly.
* @param proj A geographic projection for displaying the themes.
*/
public void setProjection(Projection proj) {
scale.setProjection(proj);
}
/**
* Gets the scaler that is being used to transform the map to and from the screen.
* @return Scaler used to transform map to and from screen.
*/
public Scaler getScale() {
return this.scale;
}
/**
* Navigation tool methods.
* @param b The new bounds for the navigation extent
* @deprecated see uk.ac.leeds.ccg.geotools.NavigateTool for details.
*/
public void setNavigationBounds(GeoRectangle b){
if(tool instanceof NavigateTool){
((NavigateTool)tool).setNavigationBounds(b);
}
}
/**
* Navigation tool methods.
* @return The current navigaition bounds
* @deprecated see uk.ac.leeds.ccg.geotools.NavigateTool for details.
*/
public GeoRectangle getNavigationBounds(){
if(tool instanceof NavigateTool){
return ((NavigateTool)tool).getNavigationBounds();
}
return null;
}
/**
* Navigation tool methods.
* @param v The viewer to be controlled by this one.
* @deprecated see uk.ac.leeds.ccg.geotools.NavigateTool for details.
*/
public void setNavigationTarget(Viewer v){
if(tool instanceof NavigateTool){
if(debug)System.out.println("V--->Setting nav target in nav tool");
((NavigateTool)tool).setTarget(v);
}
}
/**
* Set which tool mode we are using.
* @param mode A tool mode constant.
* @deprecated use uk.ac.leeds.ccg.geotools.Viewer#setTool instead.
*/
public void setToolMode(int mode){
switch(mode){
case(SELECT):
setTool(new SelectTool());
break;
case(ZOOM):
setTool(new ZoomTool());
break;
case(PAN):
setTool(new PanTool());
break;
case(NAVIGATE):
setTool(new NavigateTool());
break;
}
}
/**
* Sets the active tool for this viewer.
* Call this method to change the tool for this viewer. Tools available by default include
* ZoomTool, PanTool and SelectTool.
*
An example call would be:
* view.setTool(new ZoomTool());
* @param t The new tool to use.
* @since 0.7.7.2
*/
public void setTool(Tool t){
tool =t;
tool.setContext(this);
setCursor(tool.getCursor());
}
/**
* Returns the active tool being used by this viewer.
* @return Active tool being used by this viewer.
*/
public Tool getTool() {
return tool;
}
/**
* Updates the component.
* @param g Graphics object.
*/
public void update(Graphics g){
paint(g);
}
/** Gets all the static themes in this viewer in display order.
* @return A Vector of all of the themes.
*/
public Vector getThemes(){
ThemeStack.ThemeInfo[] infos = themeStack.getOrderedThemeInfos();
Vector v = new Vector();
for(int i=0;iPainting!");
if(this.getBounds().width <=0){if(debug)System.out.println("V--->Viewer of zero Size");return;}
if(themeCount>0 && !lastSize.equals(this.getBounds())){
if(debug)System.out.println("V--->"+name+"Hey!, who changed my size!");
if(debug)System.out.println("V--->"+name+"Last "+lastSize);
if(debug)System.out.println("V--->"+name+"New "+getBounds());
Rectangle oldSize = new Rectangle(lastSize);
setupScale();
// EXPERIMENTAL CODE
// if the new & the old size have the same width & height
// there might be no reason to flush all the buffers.
if(oldSize.width!=getBounds().width || oldSize.height!=getBounds().height) {
screenBuffer=null;
panBuffer=null;
selectionBuffer=null;
if(staticBuffer!=null){
staticBuffer=null;
}
// moved by ian so rendering off screen will work
updateStaticBuffer();
if(sequenceTheme!=null){
createSequenceBuffer(sequenceTheme.length);
}
}
}
//Go through each of the themes, add them to the buffer and then plot the buffer
if(screenBuffer == null){
screenBuffer = this.createImage(getBounds().width,getBounds().height);
}
if(screenBuffer ==null)return;
if(debug)System.out.println("V--> screenBuffer "+screenBuffer);
Graphics sg = screenBuffer.getGraphics();
if(debug)System.out.println("V--> staticBuffer "+staticBuffer);
if(staticBuffer != null){
if(selectionChanged){
if(debug){System.out.println("V--->Call selections? ");}
updateSelectionBuffer();
}
if(selectionBuffer != null){
sg.drawImage(selectionBuffer,0,0,this);
}
}
//add sequenceBuffer frame x
//try{
if(sequenceBuffer!=null && sequenceBuffer[frame]!=null){
if(!sequenceFrameValid[frame]){updateSequenceBuffer(frame);}
sg.drawImage(sequenceBuffer[frame],0,0,this);
}
//sequenceTheme[frame].paintHighlight(sg,scale);//experiment
//}catch(Exception e){}
//add selections to the static buffer?
//paintSelections(sg);
paintHighlights(sg);
//add animation on top
if(animationTheme != null)
animationTheme.paintScaled(sg,scale);
//finaly add any tool related things
if(showTips && mouseStatus.isMouseStill() && mouseStatus.isPointerInside()){
String tip ="";
Enumeration e = visibleThemes.elements();
while(e.hasMoreElements()){
Theme theme = (Theme)e.nextElement();
if(isThemeVisible(theme)){
String tempTip = (theme).getTipText(getMapGeoPoint(),this.scale);
if(tempTip !=null && !tempTip.trim().equals("")){
tip = tempTip;
}
}
}
if(animationTheme !=null){
String tempTip = (animationTheme.getTipText(getMapGeoPoint(),this.scale));
if (tempTip !=null && !tempTip.trim().equals("")){
tip = tempTip;
}
}
if(!tip.trim().equals("")){paintTip(sg,tip);}
}
tool.paint(sg);
//now we can plot to screen
if(debug){System.out.println("V--->Plot to the screen");}
g.drawImage(screenBuffer,0,0,this);
}
/**
* A quick method to handle the effect of a theme's contents changing.
* Unfinished !!!
* @param tce A Theme Changed Event.
*/
public void themeChanged(ThemeChangedEvent tce){
if(debug)System.out.println("V--->"+name+"Update for reason code "+tce.getReason());
if(tce.getReason()==tce.GEOGRAPHY){
if(debug)System.out.println("V--->"+name+"Adding a theme to Bounds\n"+fullMapExtent);
if(fullMapExtent.height==0 || fullMapExtent.width==0){
fullMapExtent.add(((Theme)tce.getSource()).getBounds());
this.setMapExtentFull();
}
else{
fullMapExtent.add(((Theme)tce.getSource()).getBounds());
}
if(debug)System.out.println("V--->"+name+"Added a theme to Bounds\n"+fullMapExtent);
updateStaticBuffer();
}
//scale.setMapExtent(
if(tce.getReason()==tce.DATA || tce.getReason()==tce.SHADE){
updateStaticBuffer();
}
if(tce.getReason()==tce.SELECTION){
selectionChanged=true;
}
if(tce.getReason()==tce.ANIMATION){
// System.out.println("Animation Frame");
}
repaint();
}
/**
* Causes contents to be redrawn.
*/
public void repaint(){
update(this.getGraphics());
}
/**
* Paints a small tooltip.
* @param g A Graphics context for the tip to be rendered in.
* @param s The text to display in the tooltip.
*/
public void paintTip(Graphics g,String s){
s=s.trim();
int maxr = this.getBounds().width;
int x = mouseStatus.screen_xy[0];
int y = mouseStatus.screen_xy[1];
FontMetrics fm = g.getFontMetrics();
fm.stringWidth(s);
int h= fm.getHeight();
int xOffset = 5;
int yOffset = -h-2;
int xPad = 10;
int yPad = 4;
x+=xOffset;
y+=yOffset;
if(x+fm.stringWidth(s)+xPad>maxr){
// System.out.print("Correcting "+x);
int dif = maxr-(x+fm.stringWidth(s)+xPad);
x=x+dif;
// System.out.println(" "+x+" "+dif);
}
//System.out.println("Painting tip'"+s+"'");
g.setColor(Color.black);
g.drawRect(x,y,fm.stringWidth(s)+xPad,h+yPad);
g.setColor(new Color(.8f,.8f,.4f));
g.fillRect(x,y,fm.stringWidth(s)+xPad,h+yPad);
g.setColor(Color.black);
g.drawString(s,x+(xPad/2),y+h-yPad/2);
}
/**
* @param args */
static public void main(String args[]) {
class DriverFrame extends java.awt.Frame {
public DriverFrame() {
addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent event) {
dispose(); // free the system resources
System.exit(0); // close the application
}
});
setLayout(new java.awt.BorderLayout());
setSize(300,300);
add(new Viewer());
}
}
new DriverFrame().show();
}
class ViewerMouse extends java.awt.event.MouseAdapter {
/**
* This MouseEvent occurs when the mouse cursor enters a component's area.
* @param event Indicates the mouse cursor has entered a component's area.
*/
public void mouseEntered(java.awt.event.MouseEvent event) {
Object object = event.getSource();
if (object == Viewer.this)
Viewer_MouseEntered(event);
}
/**
* This MouseEvent occurs when a mouse button is pressed and released.
* @param event Indicates a mouse button has been pressed and released.
*/
public void mouseClicked(java.awt.event.MouseEvent event) {
Object object = event.getSource();
if (object == Viewer.this)
//notifyClickEvent();
Viewer_MouseClicked(event);
}
/**
* This MouseEvent occurs when a mouse button is let up.
* @param event Indicates mouse button has been let up.
*/
public void mouseReleased(java.awt.event.MouseEvent event) {
Object object = event.getSource();
if (object == Viewer.this)
Viewer_MouseRelease(event);
}
/**
* This MouseEvent occurs when the mouse cursor leaves a component's area.
* @param event Indicates mouse cursor has left component.
*/
public void mouseExited(java.awt.event.MouseEvent event) {
Object object = event.getSource();
if (object == Viewer.this)
Viewer_MouseExit(event);
}
}
void Viewer_MouseExit(java.awt.event.MouseEvent event) {
if(highlightMode == INSTANT)notifyHighlightPositionChanged(null);
}
class ViewerMouseMotion extends java.awt.event.MouseMotionAdapter {
/**
* This MouseMotionEvent occurs when the mouse position changes.
* @param event Indicates mouse position has changed.
*/
public void mouseMoved(java.awt.event.MouseEvent event) {
Object object = event.getSource();
if (object == Viewer.this)
Viewer_MouseMove(event);
}
/**
* This MouseMotionEvent occurs when the mouse position changes while the "drag" modifier is active (for example, the shift key).
* @param event Indicates mouse position has changed while "drag" modifier active.
*/
public void mouseDragged(java.awt.event.MouseEvent event) {
if(themeCount <1){return;}
tool.update(getToolGraphics(),tool.M_DRAG);
}
}
void Viewer_MouseRelease(java.awt.event.MouseEvent event) {
if(themeCount <1){return;}
tool.update(getToolGraphics(),tool.M_RELEASE);
}
/**
* Change the scale of the map.
* @param sce Scale Changed Event.
*/
public void scaleChanged(ScaleChangedEvent sce){
Scaler s = (Scaler)sce.getSource();
if(debug)System.out.println("V--->"+name+"Viewer reports that scale has changed");
if(s==scale){
if(debug)System.out.println("V--->"+name+" updating due to scale change");
updateStaticBuffer();
updateSelectionBuffer();
invalidateSequenceBuffer();
update(this.getGraphics());
}
}
void Viewer_MouseMove(java.awt.event.MouseEvent event) {
if(themeCount >0){
tool.update(getToolGraphics(),tool.M_MOVE);
if(highlightMode == INSTANT)notifyHighlightPositionChanged(mouseStatus.getMapPoint());
}
}
/**
* Add IDChangedListener.
* @param icl IDChangedListener to add.
*/
public synchronized void
addIDChangedListener(IDChangedListener icl) {
listeners.addElement(icl);
}
/** Remove IDChangedListener.
* @param icl IDChangedListener to remove.
*/
public synchronized void
removeIDChangedListeners(IDChangedListener icl) {
listeners.removeElement(icl);
}
/**
* Notifies IDChangedEvent to listeners.
*/
protected void notifyIDChanged() {
Vector l;
IDChangedEvent ice = new IDChangedEvent(this,currentID);
synchronized(this) {l = (Vector)listeners.clone(); }
for (int i = 0; i < l.size();i++) {
((IDChangedListener)l.elementAt(i)).idChanged(ice);
}
}
/**
* Add Highlight Position Changed Listener.
* @param hpcl Highlight Position Changed Listener to add.
*/
public synchronized void
addHighlightPositionChangedListener(HighlightPositionChangedListener hpcl) {
hpcListeners.addElement(hpcl);
}
/**
* Remove Highlight Position Changed Listener.
* @param hpcl Highlight Position Changed Listener to remove.
*/
public synchronized void
removeHighlightPositionChangedListener(HighlightPositionChangedListener hpcl) {
hpcListeners.removeElement(hpcl);
}
/**
* Notifies HighlightPositionChanged event to listener.
* @param p The new position to highlight as a point. */
protected void notifyHighlightPositionChanged(GeoPoint p) {
Vector l;
if(debug)System.out.println("V--->Hilight note "+highlightMode);
HighlightPositionChangedEvent hpce = new HighlightPositionChangedEvent(this,p);
synchronized(this) {l = (Vector)hpcListeners.clone(); }
for (int i = 0; i < l.size();i++) {
((HighlightPositionChangedListener)l.elementAt(i)).highlightPositionChanged(hpce);
}
if(sequenceBuffer!=null && sequenceBuffer[frame]!=null){
((HighlightPositionChangedListener)sequenceTheme[frame]).highlightPositionChanged(hpce);
}
}
/**
* Add Selection Position Changed Listener.
* @param spcl Selection Position Changed Listener to add.
*/
public synchronized void
addSelectionPositionChangedListener(SelectionPositionChangedListener spcl) {
spcListeners.addElement(spcl);
}
/**
* Remove Selection Position Changed Listener.
* @param spcl Selection Position Changed Listener to remove.
*/
public synchronized void
removeSelectionPositionChangedListener(SelectionPositionChangedListener spcl) {
spcListeners.removeElement(spcl);
}
/**
* Notifies SelectionPositionChanged event to listener.
* @param p The new position to select as a point */
protected void notifySelectionPositionChanged(GeoPoint p) {
if(debug)System.out.println("V--->Notifying Selection Position changed listerners");
Vector l;
if(debug)System.out.println("V--->Hilight note "+highlightMode);
SelectionPositionChangedEvent spce = new SelectionPositionChangedEvent(this,p);
synchronized(this) {l = (Vector)spcListeners.clone(); }
for (int i = 0; i < l.size();i++) {
((SelectionPositionChangedListener)l.elementAt(i)).selectionPositionChanged(spce);
}
if(sequenceBuffer!=null && sequenceBuffer[frame]!=null){
((SelectionPositionChangedListener)sequenceTheme[frame]).selectionPositionChanged(spce);
}
}
/**
* Add Composition Changed Listener.
* @param ccl Composition Changed Listener to add.
*/
public synchronized void
addCompositionChangedListener(CompositionChangedListener ccl) {
compositionChangedListeners.addElement(ccl);
}
/**
* Remove Composition Changed Listener.
* @param ccl Composition Changed Listener to remove.
*/
public synchronized void
removeCompositionChangedListener(CompositionChangedListener ccl) {
compositionChangedListeners.removeElement(ccl);
}
/**
* Notifies CompositionChanged event to listener.
* @param reason Reason code associated with change.
*/
protected void notifyCompositionChanged(int reason) {
if(debug)System.out.println("V--->Notifying Composition changed listerners");
Vector l;
if(debug)System.out.println("V--->Composition note "+highlightMode);
CompositionChangedEvent cce = new CompositionChangedEvent(this,reason);
synchronized(this) {l = (Vector)compositionChangedListeners.clone(); }
for (int i = 0; i < l.size();i++) {
((CompositionChangedListener)l.elementAt(i)).compositionChanged(cce);
}
}
/**
* Gets a ThemePanel which shows the organisation of the themes in this viewer.
* @return The ThemePanel associated with this viewer.
*/
public ThemePanel getThemePanel(){
ThemePanel tp = new ThemePanel(getThemes(),this);
addCompositionChangedListener(tp);
return tp;
}
/**
* Add Viewer Clicked Listener.
* @param vcl Viewer Clicked Listener to add.
*/
public synchronized void
addViewerClickedListener(ViewerClickedListener vcl) {
clickedListeners.addElement(vcl);
}
/**
* Remove Viewer Clicked Listener.
* @param vcl Viewer Clicked Listener to remove.
*/
public synchronized void
removeViewerClickedListener(ViewerClickedListener vcl) {
clickedListeners.removeElement(vcl);
}
/**
* Notifies ClickEvent to listeners.
*/
protected void notifyClickEvent() {
Vector list;
ViewerClickedEvent vce = new ViewerClickedEvent(this,new GeoPoint(mouseStatus.getMapPoint()));
synchronized(this) {list = (Vector)clickedListeners.clone(); }
for (int i = 0; i < list.size();i++) {
((ViewerClickedListener)list.elementAt(i)).viewerClicked(vce);
}
}
void Viewer_MouseClicked(java.awt.event.MouseEvent event) {
notifyClickEvent();
tool.update(getToolGraphics(),tool.M_CLICK);
if(highlightMode==Viewer.REQUEST)notifyHighlightPositionChanged(mouseStatus.getMapPoint());
}
void Viewer_MouseEntered(java.awt.event.MouseEvent event) {
// to do: code goes here.
}
/**
* Sets the name of the viewer.
* @param n The name of the viewer.
*/
public void setName(String n){
name=n;
}
/**
* Gets the name of the Viewer.
* @return The name of the viewer.
*/
public String getName(){
return name;
}
/**
* Add Selection Region Changed Listener.
* @param srcl Selection Region Changed Listener to add.
*/
public synchronized void
addSelectionRegionChangedListener(SelectionRegionChangedListener srcl) {
srcListeners.addElement(srcl);
}
/**
* Remove Selection Region Changed Listener.
* @param srcl Selection Region Changed Listener to remove.
*/
public synchronized void
removeSelectionRegionChangedListener(SelectionRegionChangedListener srcl) {
srcListeners.removeElement(srcl);
}
/**
* A special Selection Region Changed Listener that will be notified only after all other
* Region Changed Listeners have been notified...
* @param selectionregionchangedlistener Listener to be set.
*/
public void setFinalSelectionRegionChangedListener(SelectionRegionChangedListener selectionregionchangedlistener)
{
srcFinal = selectionregionchangedlistener;
}
/**
* Notifies SelectionRegionChanged event to listener.
* @param r The new selection region.
*/
protected void notifySelectionRegionChanged(GeoRectangle r) {
Vector l;
//if(debug)System.out.println("V--->Hilight note "+highlightMode);
SelectionRegionChangedEvent hpce = new SelectionRegionChangedEvent(this,r);
synchronized(this) {l = (Vector)srcListeners.clone(); }
for (int i = 0; i < l.size();i++) {
((SelectionRegionChangedListener)l.elementAt(i)).selectionRegionChanged(hpce);
}
if(sequenceBuffer!=null && sequenceBuffer[frame]!=null){
((SelectionRegionChangedListener)sequenceTheme[frame]).selectionRegionChanged(hpce);
}
if(srcFinal != null)
srcFinal.selectionRegionChanged(hpce);
}
/**
* Ends the print job once it is no longer referenced.
*/
public void finalize(){
if(debug)System.out.println("V--->Cleaning up");
mouseStatus = null;
}
class ComponentAdapt extends java.awt.event.ComponentAdapter {
boolean done = false;
/**
* The first time the viewer appears on the screen it is often empty.
* Once a zoom, reset or pan has been performed, the map appears properly.
* The reasons for this bug are unclear but have something to do with the viewer
* setting up the scaler before it exists on the screen, making calculations based on
* having zero size. This method gets called when a component is resized. This includes
* its first appearance on the screen. As it stands, it forces a repaint() of the viewer
* and updates the static buffers. This is slow and need not happen again after the viewer
* has been displayed for the first time, as internal methods handle the situation if
* the viewer is resized after its first apperance.
* This is not a good fix, but it does appear to work.
* @param event Indicates component has been resized.
*/
public void componentResized(java.awt.event.ComponentEvent event){
if(done)return;
updateStaticBuffer();
repaint();
done=true;
}
}
/**
* Sets theme to bottom of stack.
* @param t Theme to be set.
*/
public void setThemeToBottom(Theme t){
themeStack.setToBottom(t);
updateStaticBuffer();//this needs to be automated
notifyCompositionChanged(CompositionChangedEvent.ORDER);
repaint();
}
/**
* Sets weighting of theme to define position in stack.
* @param t Theme to be set.
* @param waight Weight of theme.
*/
public void setThemeWaighting(Theme t,int waight){
themeStack.setWaight(t,waight);
updateStaticBuffer();//this needs to be automated
notifyCompositionChanged(CompositionChangedEvent.ORDER);
repaint();
//themeWaights.remove(t);
//themeWaights.put(t,new Integer(waight));
}
/**
* Gets the weighting of a theme.
* @param t Theme to get weighting for.
* @return Weight of theme.
*/
public int getThemeWaighting(Theme t){
return themeStack.getWaight(t);
//return ((Integer)themeWaights.get(t)).intValue();
}
/**
* Swaps the positions of two themes in a stack.
* @param a Theme a.
* @param b Theme b.
*/
public void swapThemes(Theme a,Theme b){
themeStack.swapThemes(a,b);
updateStaticBuffer();//this needs to be automated
repaint();
notifyCompositionChanged(CompositionChangedEvent.ORDER);
}
/**
* Swaps the positions of two themes in a stack.
* @param a Weight of theme a.
* @param b Weight of theme b.
*/
public void swapThemes(int a,int b){
themeStack.swapThemes(a,b);
updateStaticBuffer();//this needs to be automated
repaint();
notifyCompositionChanged(CompositionChangedEvent.ORDER);
}
/** Getter for property themeStack.
* @return Value of property themeStack.
*
*/
public uk.ac.leeds.ccg.geotools.ThemeStack getThemeStack() {
return themeStack;
}
}