/* * 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; } }