//$HeadURL$
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2008 by:
Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.igeo.views.swing.map;
import static java.awt.Color.WHITE;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.OverlayLayout;
import javax.swing.Timer;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.utils.MapTools;
import org.deegree.igeo.ApplicationContainer;
import org.deegree.igeo.ChangeListener;
import org.deegree.igeo.ValueChangedEvent;
import org.deegree.igeo.commands.digitize.DeleteFeatureCommand;
import org.deegree.igeo.commands.digitize.MoveFeatureCommand;
import org.deegree.igeo.commands.digitize.UpdateFeatureCommand;
import org.deegree.igeo.commands.model.ZoomCommand;
import org.deegree.igeo.config.ViewFormType;
import org.deegree.igeo.i18n.Messages;
import org.deegree.igeo.mapmodel.Layer;
import org.deegree.igeo.mapmodel.LayerChangedEvent;
import org.deegree.igeo.mapmodel.LayerGroup;
import org.deegree.igeo.mapmodel.MapModel;
import org.deegree.igeo.mapmodel.MapModelChangedEvent;
import org.deegree.igeo.mapmodel.MapModelVisitor;
import org.deegree.igeo.modules.IModule;
import org.deegree.igeo.settings.Settings;
import org.deegree.igeo.views.DialogFactory;
import org.deegree.igeo.views.IView;
import org.deegree.igeo.views.LayerPane;
import org.deegree.igeo.views.ViewException;
import org.deegree.igeo.views.swing.ControlElement;
import org.deegree.igeo.views.swing.Footer;
import org.deegree.igeo.views.swing.MapMouseCoordsFooterEntry;
import org.deegree.igeo.views.swing.PopUpMenu;
import org.deegree.igeo.views.swing.util.PopUpRegister;
import org.deegree.kernel.CommandProcessedEvent;
import org.deegree.kernel.CommandProcessedListener;
import org.deegree.model.Identifier;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.EnvelopeImpl;
/**
*
* <code>DefaultMapComponent</code>
*
* @author <a href="mailto:buesching@lat-lon.de">Lyn Buesching</a>
* @author last edited by: $Author$
*
* @version $Revision$, $Date$
*
*/
public class DefaultMapComponent extends JComponent implements IView<Container>, ChangeListener,
CommandProcessedListener, MapModelVisitor {
private static final long serialVersionUID = 417784357523678071L;
private static final ILogger LOG = LoggerFactory.getLogger( DefaultMapComponent.class );
private String mmcFooterentryName = "MapMouseCoordinates";
private IModule<Container> owner;
MapModel mapModel;
private ApplicationContainer<Container> appContainer;
private PopUpMenu popup;
private ControlElement popUpController;
private Footer privateFooter;
private static MapMouseCoordsLabel mapCoordsLabel;
private Map<String, LayerPane> panes = new HashMap<String, LayerPane>();
transient private Map<String, BufferedImage> images = new HashMap<String, BufferedImage>();
private HighlightRenderer highlightRenderer;
/**
*
*
*/
public DefaultMapComponent() {
setLayout( new OverlayLayout( this ) );
setDoubleBuffered( true );
setVisible( true );
addComponentListener( new DMCComponentListener() );
}
/**
* @return popup controller
*/
public ControlElement getPopUpController() {
return this.popUpController;
}
/**
*
* @param footer
*/
public void setPrivateFooter( Footer footer ) {
this.privateFooter = footer;
}
/**
* @return the privateFooter
*/
public Footer getPrivateFooter() {
return privateFooter;
}
/**
* @return the name of the footer entry, showing th map mouse coordinates of this map component
*/
public String getMmcFooterentryName() {
return mmcFooterentryName;
}
@Override
public void paint( Graphics g ) {
// clear the background
// seems to be crazy - but works...
( (Graphics2D) g ).setBackground( WHITE );
g.clearRect( 0, 0, this.mapModel.getTargetDevice().getPixelWidth(),
this.mapModel.getTargetDevice().getPixelHeight() );
super.paint( g );
highlightRenderer.highlightFeatures( g );
}
/**
* Handles changes of order of the layer components of the map (layer inserted, removed or layer order changes).
*
* @throws ViewException
*
*/
private void reorderLayers()
throws ViewException {
panes.clear();
images.clear();
Component[] comps = getComponents();
for ( int i = 0; i < comps.length; i++ ) {
if ( comps[i] instanceof LayerComponent ) {
LayerPane lp = ( (LayerComponent) comps[i] ).getLayerPane();
if ( mapModel.getLayerByIdentifier( lp.getLayer().getIdentifier() ) == null ) {
// this is necessary to ensure proper gc!
( (LayerComponent) comps[i] ).destroy();
lp.getLayer().destroy();
mapModel.removeChangeListener( lp );
continue;
}
panes.put( lp.getLayer().getIdentifier().getAsQualifiedString(), lp );
images.put( lp.getLayer().getIdentifier().getAsQualifiedString(),
( (LayerComponent) comps[i] ).getImage() );
}
}
// remove all layer components
Component[] components = getComponents();
List<Component> tmp = new ArrayList<Component>();
// first collect components to remove ...
for ( int i = 0; i < components.length; i++ ) {
if ( components[i] instanceof LayerComponent ) {
tmp.add( getComponent( i ) );
}
}
// ... than remove them
for ( Component component : tmp ) {
remove( component );
}
try {
mapModel.walkLayerTree( this );
} catch ( Exception e ) {
throw new ViewException( e.getMessage(), e );
}
}
/*
* (non-Javadoc)
*
* @see org.deegree.igeo.mapmodel.MapModelVisitor#visit(org.deegree.igeo.mapmodel.Layer)
*/
public void visit( Layer layer )
throws Exception {
LayerComponent layerComponent = null;
if ( panes.get( layer.getIdentifier().getAsQualifiedString() ) != null ) {
layerComponent = new LayerComponent( panes.get( layer.getIdentifier().getAsQualifiedString() ) );
layerComponent.setForceDeepRepaint( false );
layerComponent.setImage( images.get( layer.getIdentifier().getAsQualifiedString() ) );
} else {
layerComponent = createNewLayerComponent( layer );
}
layerComponent.setBounds( 0, 0, this.mapModel.getTargetDevice().getPixelWidth(),
this.mapModel.getTargetDevice().getPixelHeight() );
add( layerComponent );
}
/*
* (non-Javadoc)
*
* @see org.deegree.igeo.mapmodel.MapModelVisitor#visit(org.deegree.igeo.mapmodel.LayerGroup)
*/
public void visit( LayerGroup layerGroup )
throws Exception {
// do nothing
}
/**
* Create a new JLayerComponent out of the layerId of the layer adapter and add the component to the map component.
* This map component class will be registered as change listener to be informed about changes of the map model.
*
* @param layer
* layer
* @throws ViewException
*/
private LayerComponent createNewLayerComponent( Layer layer )
throws ViewException {
// create a JLayerPanel out of the layer adapter
LayerPane lp = new LayerPane();
lp.setModel( this.mapModel, layer );
lp.setParentModule( this.owner );
// assign LayerPane as listener to its layer to be informed about changes that
// requires a repainting
layer.addChangeListener( lp );
LayerComponent layerComponent = new LayerComponent( lp );
layerComponent.setBounds( 0, 0, this.mapModel.getTargetDevice().getPixelWidth(),
this.mapModel.getTargetDevice().getPixelHeight() );
// add current layer component to the container
return layerComponent;
}
// /////////////////////////////////////////////////////////////////////////////////
// IView
// /////////////////////////////////////////////////////////////////////////////////
/**
* @param viewForm
* @throws Exception
*/
public void init( ViewFormType viewForm )
throws Exception {
appContainer = this.owner.getApplicationContainer();
appContainer.getCommandProcessor().addCommandProcessedListener( this );
String mmId = owner.getInitParameter( "assignedMapModel" );
this.mapModel = appContainer.getMapModel( new Identifier( mmId ) );
// register as change listener to be informed when assigned map model changes
this.mapModel.addChangeListener( this );
// adjust size and extent of the map model before painting the map
MapTools.adjustMapModelExtent( this.mapModel.getTargetDevice().getPixelWidth(),
this.mapModel.getTargetDevice().getPixelHeight(), this.mapModel );
// add JLayerPanels
mapModel.walkLayerTree( this );
// write the coordinates of the mouse in the footer
// mapCoordsLabel must be created if it's null or if wether the global footer
// not the private/local footer of a map window contains a map coordinate label
if ( mapCoordsLabel == null ) {
mapCoordsLabel = new MapMouseCoordsLabel( appContainer );
if ( privateFooter != null && !privateFooter.hasEntry( getMmcFooterentryName() ) ) {
privateFooter.addEntry( new MapMouseCoordsFooterEntry( getMmcFooterentryName(), mapCoordsLabel ) );
} else if ( appContainer.getFooter() != null
&& !appContainer.getFooter().hasEntry( getMmcFooterentryName() ) ) {
appContainer.getFooter().addEntry( new MapMouseCoordsFooterEntry( getMmcFooterentryName(),
mapCoordsLabel ) );
}
} else {
if ( privateFooter != null && !privateFooter.hasEntry( getMmcFooterentryName() ) ) {
privateFooter.addEntry( new MapMouseCoordsFooterEntry( getMmcFooterentryName(), mapCoordsLabel ) );
} else if ( appContainer.getFooter() != null
&& !appContainer.getFooter().hasEntry( getMmcFooterentryName() ) ) {
appContainer.getFooter().addEntry( new MapMouseCoordsFooterEntry( getMmcFooterentryName(),
mapCoordsLabel ) );
}
}
mapCoordsLabel.updateCRSList();
highlightRenderer = new HighlightRenderer( mapModel, this );
// register as mouseMotionListener to update the mouse coordinates when mouse moves
addMouseMotionListener( new DMCMouseMotionListener() );
// register keylister to zoom and pan map via keyboard
addKeyListener( new DMCKeyListener() );
// register mouse listener to request window focus
addMouseListener( new DMCMouseListener() );
// register mouse wheel listener to perform zooming by mouse wheel
addMouseWheelListener( new DMCMouseWheelListener() );
setPreferredSize( new Dimension( this.mapModel.getTargetDevice().getPixelWidth(),
this.mapModel.getTargetDevice().getPixelHeight() ) );
}
@Override
public boolean isFocusable() {
return true;
}
/*
* (non-Javadoc)
*
* @see org.deegree.igeo.views.IView#registerModule(org.deegree.client.application.modules.IModule)
*/
public void registerModule( IModule<Container> module ) {
this.owner = module;
popUpController = PopUpRegister.registerPopups( module.getApplicationContainer(), this, owner, popUpController,
new DMCPopupListener() );
popup = (PopUpMenu) popUpController.getView();
}
/**
* performs an update by invoking {@link #repaint()}
*/
public void update() {
repaint();
}
// /////////////////////////////////////////////////////////////////////////////////
// ComandProcessedListener
// /////////////////////////////////////////////////////////////////////////////////
/*
* (non-Javadoc)
*
* @see
* org.deegree.client.presenter.connector.CommandProcessedListener#commandProcessed(org.deegree.client.presenter
* .connector.CommandProcessedEvent)
*/
public void commandProcessed( CommandProcessedEvent event ) {
if ( event.getSource() instanceof MoveFeatureCommand || event.getSource() instanceof UpdateFeatureCommand
|| event.getSource() instanceof DeleteFeatureCommand ) {
for ( int i = 0; i < getComponentCount(); i++ ) {
if ( getComponent( i ) instanceof LayerComponent ) {
// forces the repaint of a layer
( (LayerComponent) getComponent( i ) ).setForceDeepRepaint( true );
}
}
}
repaint();
}
// /////////////////////////////////////////////////////////////////////////////////
// ChangeListener
// /////////////////////////////////////////////////////////////////////////////////
public void valueChanged( ValueChangedEvent event ) {
if ( event instanceof MapModelChangedEvent ) {
MapModelChangedEvent mapModelEvent = (MapModelChangedEvent) event;
switch ( mapModelEvent.getChangeType() ) {
case layerInserted:
case layerOrderChanged:
case layerRemoved:
case layerGroupRemoved:
case layerGroupInserted:
try {
reorderLayers();
} catch ( ViewException e ) {
throw new RuntimeException( e.getMessage(), e );
}
repaint();
break;
case extentChanged:
case crsChanged:
case targetDeviceChanged:
for ( int i = 0; i < getComponentCount(); i++ ) {
if ( getComponent( i ) instanceof LayerComponent ) {
( (LayerComponent) getComponent( i ) ).setForceDeepRepaint( true );
}
}
repaint();
break;
case layerStateChanged:
event = (LayerChangedEvent) mapModelEvent.getEmbeddedEvent();
break;
}
}
if ( event instanceof LayerChangedEvent ) {
LayerChangedEvent lce = (LayerChangedEvent) event;
switch ( lce.getChangeType() ) {
case dataChanged:
case datasourceAdded:
case datasourceRemoved:
case scaleRangeChanged:
case stylesSet:
Layer layer = lce.getSource();
for ( int i = 0; i < getComponentCount(); i++ ) {
if ( getComponent( i ) instanceof LayerComponent ) {
Layer tmp = ( (LayerComponent) getComponent( i ) ).getLayerPane().getLayer();
if ( tmp.getIdentifier().equals( layer.getIdentifier() ) ) {
( (LayerComponent) getComponent( i ) ).setForceDeepRepaint( true );
break;
}
}
}
repaint();
case visibilityChanged:
repaint();
break;
case featureSelected:
FeatureCollection fc = ( (Layer) lce.getSource() ).getSelectedFeatures();
List<FeatureCollection> selectedFeatures = new ArrayList<FeatureCollection>();
selectedFeatures.add( fc );
highlightRenderer.setSelectedFeatures( selectedFeatures );
repaint();
break;
case featureUnselected:
highlightRenderer.setSelectedFeatures( null );
repaint();
break;
}
}
}
// /////////////////////////////////////////////////////////////////////////////////
// inner class //
// /////////////////////////////////////////////////////////////////////////////////
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
private class DMCPopupListener extends MouseAdapter {
@Override
public void mousePressed( MouseEvent e ) {
maybeShowPopup( e );
}
@Override
public void mouseReleased( MouseEvent e ) {
maybeShowPopup( e );
}
private void maybeShowPopup( MouseEvent e ) {
if ( e.isPopupTrigger() ) {
popup.show( e.getComponent(), e.getX(), e.getY() );
}
}
}
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
public class DMCMouseMotionListener implements MouseMotionListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
*/
public void mouseDragged( MouseEvent event ) {
mapCoordsLabel.setMouseCoords( event.getX(), event.getY(), getWidth(), getHeight() );
}
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
*/
public void mouseMoved( MouseEvent event ) {
mapCoordsLabel.setMouseCoords( event.getX(), event.getY(), getWidth(), getHeight() );
}
}
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
private class DMCMouseWheelListener implements MouseWheelListener {
/*
* (non-Javadoc)
*
* @see java.awt.event.MouseWheelListener#mouseWheelMoved(java.awt.event.MouseWheelEvent)
*/
public void mouseWheelMoved( MouseWheelEvent e ) {
EnvelopeImpl tmp = (EnvelopeImpl) mapModel.getEnvelope();
Envelope env = (Envelope) tmp.clone();
float zl = appContainer.getSettings().getZoomLevel() / 100f;
ZoomCommand zc = new ZoomCommand( mapModel );
if ( e.getWheelRotation() > 0 ) {
float sZL = appContainer.getSettings().getZoomLevel();
if ( sZL > 95 ) {
sZL = 95;
}
zl = -100f / ( ( 100f - sZL ) );
} else {
if ( zl > 0.45 ) {
// correct zoom level because a buffer can not be >= 50% of a box size
zl = -0.45f;
} else {
zl *= -1f;
}
}
int xx = mapModel.getTargetDevice().getPixelWidth() / 2;
int yy = mapModel.getTargetDevice().getPixelHeight() / 2;
zc.setZoom( xx, yy, xx, yy, zl, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
try {
appContainer.getCommandProcessor().executeSychronously( zc, true );
} catch ( Exception ex ) {
LOG.logError( ex.getMessage(), ex );
DialogFactory.openErrorDialog( appContainer.getViewPlatform(), DefaultMapComponent.this,
Messages.getMessage( getLocale(), "$MD11253" ),
Messages.getMessage( getLocale(), "$MD11252", env ), ex );
}
}
}
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
private class DMCKeyListener extends KeyAdapter {
@Override
public void keyPressed( KeyEvent e ) {
EnvelopeImpl tmp = (EnvelopeImpl) mapModel.getEnvelope();
Envelope env = (Envelope) tmp.clone();
Settings settings = appContainer.getSettings();
ZoomCommand zc = null;
if ( e.getKeyCode() == KeyEvent.VK_DOWN ) {
zc = new ZoomCommand( mapModel );
float pan = settings.getPanLevel() / 100f;
env = env.translate( 0, env.getHeight() * -1 * pan );
zc.setZoomBox( env, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
} else if ( e.getKeyCode() == KeyEvent.VK_UP ) {
zc = new ZoomCommand( mapModel );
float pan = settings.getPanLevel() / 100f;
env = env.translate( 0, env.getHeight() * pan );
zc.setZoomBox( env, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
} else if ( e.getKeyCode() == KeyEvent.VK_LEFT ) {
zc = new ZoomCommand( mapModel );
float pan = settings.getPanLevel() / 100f;
env = env.translate( env.getWidth() * -1 * pan, 0 );
zc.setZoomBox( env, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
} else if ( e.getKeyCode() == KeyEvent.VK_RIGHT ) {
zc = new ZoomCommand( mapModel );
float pan = settings.getPanLevel() / 100f;
env = env.translate( env.getWidth() * pan, 0 );
zc.setZoomBox( env, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
} else if ( e.getKeyCode() == KeyEvent.VK_PLUS ) {
zc = new ZoomCommand( mapModel );
float zl = appContainer.getSettings().getZoomLevel() / 100f;
if ( zl > 0.45 ) {
// correct zoom level because a buffer can not be >= 50% of a box size
zl = -0.45f;
} else {
zl *= -1f;
}
int xx = mapModel.getTargetDevice().getPixelWidth() / 2;
int yy = mapModel.getTargetDevice().getPixelHeight() / 2;
zc.setZoom( xx, yy, xx, yy, zl, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
} else if ( e.getKeyCode() == KeyEvent.VK_MINUS ) {
zc = new ZoomCommand( mapModel );
float sZL = appContainer.getSettings().getZoomLevel();
if ( sZL > 95 ) {
sZL = 95;
}
float zl = -100f / ( ( 100f - sZL ) );
int xx = mapModel.getTargetDevice().getPixelWidth() / 2;
int yy = mapModel.getTargetDevice().getPixelHeight() / 2;
zc.setZoom( xx, yy, xx, yy, zl, mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight() );
}
if ( zc != null ) {
try {
appContainer.getCommandProcessor().executeSychronously( zc, true );
} catch ( Exception ex ) {
LOG.logError( ex.getMessage(), ex );
DialogFactory.openErrorDialog( appContainer.getViewPlatform(), DefaultMapComponent.this,
Messages.getMessage( getLocale(), "$MD11253" ),
Messages.getMessage( getLocale(), "$MD11252", env ), ex );
}
}
}
}
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
public class DMCMouseListener extends MouseAdapter {
@Override
public void mouseEntered( MouseEvent e ) {
requestFocusInWindow( true );
}
@Override
public void mouseExited( MouseEvent e ) {
requestFocusInWindow( false );
}
}
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
class DMCComponentListener extends ComponentAdapter implements ActionListener {
private Timer timer;
private ComponentEvent event;
@Override
public synchronized void componentResized( ComponentEvent event ) {
if ( timer != null ) {
timer.stop();
}
timer = new Timer( 100, this );
this.event = event;
timer.start();
}
public void actionPerformed( ActionEvent e ) {
MapTools.adjustMapModelExtent( event.getComponent().getWidth(), event.getComponent().getHeight(), mapModel );
timer.stop();
timer = null;
}
}
}