Java tutorial
/*--------------- Kalypso-Header -------------------------------------------------------------------- This file is part of kalypso. Copyright (C) 2004, 2005 by: Technical University Hamburg-Harburg (TUHH) Institute of River and coastal engineering Denickestr. 22 21073 Hamburg, Germany http://www.tuhh.de/wb and Bjoernsen Consulting Engineers (BCE) Maria Trost 3 56070 Koblenz, Germany http://www.bjoernsen.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: E-Mail: belger@bjoernsen.de schlienger@bjoernsen.de v.doemming@tuhh.de ---------------------------------------------------------------------------------------------------*/ package org.kalypso.ogc.gml.map; import java.awt.Canvas; import java.awt.Color; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Rectangle; import java.awt.RenderingHints; import java.awt.event.ComponentEvent; import java.awt.event.ComponentListener; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ObjectUtils; import org.eclipse.core.runtime.ISafeRunnable; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.SafeRunner; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.ISchedulingRule; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.util.SafeRunnable; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.services.IDisposable; import org.kalypso.commons.command.ICommandTarget; import org.kalypso.contribs.eclipse.core.runtime.StatusUtilities; import org.kalypso.contribs.eclipse.core.runtime.jobs.MutexRule; import org.kalypso.contribs.eclipse.jobs.BufferPaintJob; import org.kalypso.contribs.eclipse.jobs.BufferPaintJob.IPaintable; import org.kalypso.contribs.eclipse.jobs.ImageCache; import org.kalypso.contribs.eclipse.jobs.JobObserverJob; import org.kalypso.contribs.eclipse.jobs.TextPaintable; import org.kalypso.core.KalypsoCoreDebug; import org.kalypso.core.KalypsoCorePlugin; import org.kalypso.core.i18n.Messages; import org.kalypso.ogc.gml.IKalypsoCascadingTheme; import org.kalypso.ogc.gml.IKalypsoFeatureTheme; import org.kalypso.ogc.gml.IKalypsoLayerModell; import org.kalypso.ogc.gml.IKalypsoTheme; import org.kalypso.ogc.gml.map.layer.BufferedRescaleMapLayer; import org.kalypso.ogc.gml.map.layer.CacscadingMapLayer; import org.kalypso.ogc.gml.map.layer.DirectMapLayer; import org.kalypso.ogc.gml.map.layer.SelectionMapLayer; import org.kalypso.ogc.gml.map.listeners.IMapPanelListener; import org.kalypso.ogc.gml.map.listeners.IMapPanelMTPaintListener; import org.kalypso.ogc.gml.map.listeners.IMapPanelPaintListener; import org.kalypso.ogc.gml.mapmodel.IKalypsoThemeVisitor; import org.kalypso.ogc.gml.mapmodel.IMapModell; import org.kalypso.ogc.gml.mapmodel.IMapModellListener; import org.kalypso.ogc.gml.mapmodel.MapModellAdapter; import org.kalypso.ogc.gml.mapmodel.MapModellHelper; import org.kalypso.ogc.gml.selection.EasyFeatureWrapper; import org.kalypso.ogc.gml.selection.IFeatureSelection; import org.kalypso.ogc.gml.selection.IFeatureSelectionListener; import org.kalypso.ogc.gml.selection.IFeatureSelectionManager; import org.kalypso.ogc.gml.widgets.IWidgetManager; import org.kalypso.ogc.gml.widgets.WidgetManager; import org.kalypsodeegree.graphics.transformation.GeoTransform; import org.kalypsodeegree.model.geometry.GM_Envelope; import org.kalypsodeegree.model.geometry.GM_Point; import org.kalypsodeegree_impl.graphics.transformation.WorldToScreenTransform; import org.kalypsodeegree_impl.model.geometry.GeometryFactory; /** * AWT canvas that displays a {@link org.kalypso.ogc.gml.mapmodel.MapModell}. * * @author Andreas von Dmming * @author Gernot Belger */ public class MapPanel extends Canvas implements ComponentListener, IMapPanel { // Rule used to force some of the layers to be rendered one after another. private final ISchedulingRule m_layerMutex = new MutexRule(); /** * Maximum delay by which repaints to the map are produced. * * @see java.awt.Component#repaint(long) */ private static final long LAYER_REPAINT_MILLIS = 500; private static interface IListenerRunnable { void visit(final IMapPanelListener l); } private static final long serialVersionUID = 1L; private final boolean m_isMultitouchEnabled; static { System.setProperty("sun.awt.noerasebackground", "true"); //$NON-NLS-1$ //$NON-NLS-2$ } private final IFeatureSelectionManager m_selectionManager; private final Collection<ISelectionChangedListener> m_selectionListeners = new HashSet<>(5); private final IFeatureSelectionListener m_globalSelectionListener = new IFeatureSelectionListener() { @Override public void selectionChanged(final Object source, final IFeatureSelection selection) { globalSelectionChanged(); } }; private IKalypsoLayerModell m_model = null; private final WidgetManager m_widgetManager; protected GM_Envelope m_boundingBox = null; private GM_Envelope m_wishBBox; private final Collection<IMapPanelListener> m_mapPanelListeners = Collections .synchronizedSet(new LinkedHashSet<IMapPanelListener>()); private final Collection<IMapPanelPaintListener> m_paintListeners = Collections .synchronizedSet(new LinkedHashSet<IMapPanelPaintListener>()); private final Collection<IMapPanelMTPaintListener> m_postPaintListeners = new HashSet<>(); private final Map<IKalypsoTheme, IMapLayer> m_layers = Collections .synchronizedMap(new HashMap<IKalypsoTheme, IMapLayer>()); private final ExtentHistory m_extentHistory = new ExtentHistory(200); private String m_message = ""; //$NON-NLS-1$ // TODO: fetch from map-modell (aka from .gmt file) private final Color m_backgroundColor = new Color(255, 255, 255); private final IMapModellListener m_modellListener = new MapModellAdapter() { @Override public void themeAdded(final IMapModell source, final IKalypsoTheme theme) { if (theme.isVisible()) setBoundingBox(getWishBox(), false, true); updateStatus(); } @Override public void themeOrderChanged(final IMapModell source) { invalidateMap(); } @Override public void themeRemoved(final IMapModell source, final IKalypsoTheme theme, final boolean lastVisibility) { handleThemeRemoved(theme, lastVisibility); } @Override public void themeVisibilityChanged(final IMapModell source, final IKalypsoTheme theme, final boolean visibility) { invalidateMap(); } @Override public void themeStatusChanged(final IMapModell source, final IKalypsoTheme theme) { } }; private BufferPaintJob m_bufferPaintJob = null; /** One mutex-rule per panel, so painting jobs for one panel run one after another. */ private final ISchedulingRule m_painterMutex = new MutexRule(); /** * An image cache is used to provide the buffered images for the panel and also the buffered layers.<br> * This reduces the impact of recreating lots of buffered images when mutiple layers get repainted. */ private final ImageCache m_imageCache = new ImageCache(1, m_backgroundColor, true); /** * An image cache is used to provide the buffered images for the panel and also the buffered layers.<br> * This reduces the impact of recreating lots of buffered images when mutiple layers get repainted. */ private final ImageCache m_layerImageCache = new ImageCache(5, new Color(255, 255, 255, 0), false); private IStatus m_status = Status.OK_STATUS; private Dimension m_size; private boolean m_useFullSelection = false; private BufferedImage m_imageBuffer = null; private IDisposable m_mtObject; public BufferedImage getBufferedImage() { if (m_imageBuffer == null) paint(null); return m_imageBuffer; } public MapPanel(final ICommandTarget viewCommandTarget, final IFeatureSelectionManager manager) { if ("true".equals(System.getProperty("org.kalypso.ogc.gml.mappanel.multitouch"))) //$NON-NLS-1$ //$NON-NLS-2$ m_isMultitouchEnabled = true; else m_isMultitouchEnabled = false; m_selectionManager = manager; m_selectionManager.addSelectionListener(m_globalSelectionListener); m_widgetManager = new WidgetManager(viewCommandTarget, this); addMouseListener(m_widgetManager); addMouseMotionListener(m_widgetManager); addMouseWheelListener(m_widgetManager); addKeyListener(m_widgetManager); addComponentListener(this); } protected final GM_Envelope getWishBox() { return m_wishBBox; } // REMARK: most probably we should always return the complete selection; filtering by themes does not make so much // sense // However, we use this flag for the moment to keep this backwards compatible and avoid side effekt. // TODO: try this out when we are far from deploying @Override public void setUseFullSelection(final boolean useFullSelection) { m_useFullSelection = useFullSelection; } /** * Runns the given runnable on every listener in a safe way. */ private void acceptListenersRunnable(final IListenerRunnable r) { final IMapPanelListener[] listeners = m_mapPanelListeners .toArray(new IMapPanelListener[m_mapPanelListeners.size()]); for (final IMapPanelListener l : listeners) { final ISafeRunnable code = new SafeRunnable() { @Override public void run() throws Exception { r.visit(l); } }; SafeRunner.run(code); } } /** * Add a listener in the mapPanel that will be notified in specific changes. <br/> * At the moment there is only the message changed event. */ @Override public void addMapPanelListener(final IMapPanelListener l) { m_mapPanelListeners.add(l); } public void addPostPaintListener(final IMapPanelMTPaintListener pl) { m_postPaintListeners.add(pl); } @Override public void addPaintListener(final IMapPanelPaintListener pl) { m_paintListeners.add(pl); } @Override public void addSelectionChangedListener(final ISelectionChangedListener listener) { m_selectionListeners.add(listener); } @Override public void componentHidden(final ComponentEvent e) { // } @Override public void componentMoved(final ComponentEvent e) { // } @Override public void componentResized(final ComponentEvent e) { /* * Bugfix: this prohibits a repaint, if the window is minimized: we do not repaint, and keep the old size. When the * component is shown again, the new size is the old size and we also do not need to paint. */ // TRICKY: we are minimized, if the location is lesser than zero. Is there another way to find this out? if (!m_isMultitouchEnabled) { final Point locationOnScreen = getLocationOnScreen(); if (locationOnScreen.x < 0 && locationOnScreen.y < 0) return; } /* Only resize, if size really changed. */ final Dimension size = getSize(); if (ObjectUtils.equals(m_size, size)) return; if (size != null && size.width == 0 && size.height == 0) return; if (ObjectUtils.equals(m_size, size)) return; m_size = size; setBoundingBox(m_wishBBox, false); } @Override public void componentShown(final ComponentEvent e) { setBoundingBox(m_wishBBox, false); } @Override public void dispose() { removeMouseListener(m_widgetManager); removeMouseMotionListener(m_widgetManager); removeMouseWheelListener(m_widgetManager); removeKeyListener(m_widgetManager); removeComponentListener(this); m_selectionManager.removeSelectionListener(m_globalSelectionListener); m_widgetManager.dispose(); if (m_mtObject != null) m_mtObject.dispose(); setMapModell(null); // REMARK: this should not be necessary, but fixes the memory leak problem when opening/closing a .gmt file. // TODO: where is this map panel still referenced from? // Anwer: From the map-commands! m_selectionListeners.clear(); m_mapPanelListeners.clear(); m_paintListeners.clear(); synchronized (this) { if (m_bufferPaintJob != null) { m_bufferPaintJob.dispose(); m_bufferPaintJob = null; } m_imageCache.clear(); } } protected void fireExtentChanged(final GM_Envelope oldExtent, final GM_Envelope newExtent) { acceptListenersRunnable(new IListenerRunnable() { @Override public void visit(final IMapPanelListener l) { l.onExtentChanged(MapPanel.this, oldExtent, newExtent); } }); } protected void fireMapModelChanged(final IKalypsoLayerModell oldModel, final IKalypsoLayerModell newModel) { acceptListenersRunnable(new IListenerRunnable() { @Override public void visit(final IMapPanelListener l) { l.onMapModelChanged(MapPanel.this, oldModel, newModel); } }); } /** * Must be invoked, if the message of the mapPanel has changed. */ private void fireMessageChanged(final String message) { acceptListenersRunnable(new IListenerRunnable() { @Override public void visit(final IMapPanelListener l) { l.onMessageChanged(MapPanel.this, message); } }); } /** * Must be invoked, if the status of the mapPanel has changed. */ private void fireStatusChanged() { acceptListenersRunnable(new IListenerRunnable() { @Override public void visit(final IMapPanelListener l) { l.onStatusChanged(MapPanel.this); } }); } protected final void fireSelectionChanged() { final ISelectionChangedListener[] listenersArray = m_selectionListeners .toArray(new ISelectionChangedListener[m_selectionListeners.size()]); final IStructuredSelection selection = (IStructuredSelection) getSelection(); final SelectionChangedEvent e = new SelectionChangedEvent(this, selection); for (final ISelectionChangedListener l : listenersArray) { final Display display = PlatformUI.getWorkbench().getDisplay(); display.syncExec(new Runnable() { @Override public void run() { final SafeRunnable safeRunnable = new SafeRunnable() { /** * Overwritten because opening the message dialog here results in a NPE * * @see org.eclipse.jface.util.SafeRunnable#handleException(java.lang.Throwable) */ @Override public void handleException(final Throwable t) { t.printStackTrace(); } @Override public void run() { l.selectionChanged(e); } }; SafeRunnable.run(safeRunnable); } }); } } @Override public GM_Envelope getBoundingBox() { return m_boundingBox; } /** * @see org.kalypso.ogc.gml.map.IMapPanel#getScreenBounds() */ @Override public Rectangle getScreenBounds() { return getBounds(); } /** * calculates the current map scale (denominator) as defined in the OGC SLD 1.0.0 specification * * @return scale of the map */ @Override public double getCurrentScale() { final GeoTransform projection = getProjection(); if (projection == null) return Double.NaN; // TODO: hot-spot: always recalculates the scale, should be cached return projection.getScale(); } @Override public IKalypsoLayerModell getMapModell() { return m_model; } @Override public String getMessage() { return m_message; } @Override public ISelection getSelection() { // See setUseFullSelection if (m_useFullSelection) return m_selectionManager; final IMapModell mapModell = getMapModell(); if (mapModell == null) return StructuredSelection.EMPTY; // REMARK: we need an own implementation, as the feature selection // did return 'Feature' objects (whereas the FeatureSelection return sEasyFeatureWrappers) return new MapPanelSelection(m_selectionManager); // final IKalypsoTheme activeTheme = mapModell.getActiveTheme(); // if( activeTheme instanceof IKalypsoFeatureTheme ) // return (ISelection) activeTheme.getAdapter( IFeatureSelection.class ); // // return StructuredSelection.EMPTY; } @Override public IFeatureSelectionManager getSelectionManager() { return m_selectionManager; } @Override public IWidgetManager getWidgetManager() { return m_widgetManager; } protected void globalSelectionChanged() { invalidateMap(); fireSelectionChanged(); } /** * Invalidates the whole map, all data is redrawn freshly.<br> * Should not be invoked from outside; normally every theme invalidates itself, if its data is changed; if not check, * if all events are correctly sent.<br> * Important: does not invalidate the theme's buffers, so this will in most cases not do what you want.. please always * invalidate the theme by correctly firing gml-events */ @Override public void invalidateMap() { synchronized (this) { /* Cancel old job if still running. */ if (m_bufferPaintJob != null) { m_bufferPaintJob.dispose(); m_bufferPaintJob = null; } final IMapModell mapModell = getMapModell(); if (mapModell == null) return; final IPaintable paintable = createPaintable(mapModell); final BufferPaintJob bufferPaintJob = new BufferPaintJob(paintable, m_imageCache); bufferPaintJob.setRule(m_painterMutex); bufferPaintJob.setPriority(Job.SHORT); bufferPaintJob.setSystem(true); /* This jobs observes the paint-job and repaints the map all 5seconds and once after painting is done */ final JobObserverJob repaintJob = new JobObserverJob("Repaint map observer", bufferPaintJob, 1000) //$NON-NLS-1$ { @Override protected void jobRunning() { MapPanel.this.repaintMap(); } }; repaintJob.setSystem(true); repaintJob.schedule(); m_bufferPaintJob = bufferPaintJob; // delay the Schedule, so if another invalidate comes within that time-span, no repaint happens at all bufferPaintJob.schedule(100); } repaintMap(); } private IPaintable createPaintable(final IMapModell mapModell) { final GeoTransform projection = getProjection(); if (projection == null) { final int width = getWidth(); final int height = getHeight(); final Point size = new Point(width, height); return new TextPaintable(size, "No Extent Set", m_backgroundColor); //$NON-NLS-1$ } final IMapLayer[] layers = getLayersForRendering(); return new MapPanelPainter(this, layers, mapModell.getLabel(), projection); } /** * Paints contents of the map in the following order: * <ul> * <li>the buffered image containing the layers</li> * <li>the status, if not OK</li> * <li>all 'paint-listeners'</li> * <li>the current widget</li> * </ul> * * @see java.awt.Component#paint(java.awt.Graphics) */ @Override public void paint(final Graphics g) { final int width = getWidth(); final int height = getHeight(); if (height == 0 || width == 0) return; // only recreate buffered image if size has changed if (m_imageBuffer == null || m_imageBuffer.getWidth() != width || m_imageBuffer.getHeight() != height) m_imageBuffer = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); else // this seems to be enough for clearing the image here m_imageBuffer.flush(); Graphics2D bufferGraphics = null; try { bufferGraphics = m_imageBuffer.createGraphics(); bufferGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); bufferGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); final BufferPaintJob bufferPaintJob = m_bufferPaintJob; // get copy (for more thread safety) if (bufferPaintJob != null) { paintBufferedMap(bufferGraphics, bufferPaintJob); } // TODO: at the moment, we paint the status just on top of the map, if we change this component to SWT, we should // show the statusComposite in a title bar, if the status is non-OK (with details button for a stack trace) paintStatus(bufferGraphics); final IMapPanelPaintListener[] pls = m_paintListeners.toArray(new IMapPanelPaintListener[] {}); for (final IMapPanelPaintListener pl : pls) pl.paint(bufferGraphics); paintWidget(bufferGraphics); } finally { if (bufferGraphics != null) bufferGraphics.dispose(); } if (m_isMultitouchEnabled) { final IMapPanelMTPaintListener[] postls = m_postPaintListeners .toArray(new IMapPanelMTPaintListener[] {}); for (final IMapPanelMTPaintListener pl : postls) pl.paint(m_imageBuffer); } else { g.drawImage(m_imageBuffer, 0, 0, null); } } private void paintBufferedMap(final Graphics2D bufferGraphics, final BufferPaintJob bufferPaintJob) { final BufferedImage image = bufferPaintJob.getImage(); if (image == null) { if (bufferPaintJob.getState() == Job.SLEEPING) { bufferPaintJob.wakeUp(0); return; } } final GM_Envelope imageBounds = imageBoundFromPaintable(bufferPaintJob); if (ObjectUtils.equals(imageBounds, m_boundingBox)) bufferGraphics.drawImage(image, 0, 0, null); else { // If current buffer only shows part of the map, paint it into the right screen-rect final GeoTransform currentProjection = getProjection(); if (currentProjection != null && imageBounds != null) MapPanelUtilities.paintIntoExtent(bufferGraphics, currentProjection, image, imageBounds, m_backgroundColor); } } private GM_Envelope imageBoundFromPaintable(final BufferPaintJob bufferPaintJob) { final IPaintable paintable = bufferPaintJob.getPaintable(); if (paintable instanceof MapPanelPainter) { final MapPanelPainter mapPaintable = (MapPanelPainter) paintable; final GeoTransform world2screen = mapPaintable.getWorld2screen(); return world2screen.getSourceRect(); } return null; } /** * @see java.awt.Component#isDoubleBuffered() */ @Override public boolean isDoubleBuffered() { return true; } /** * If a message is present, paint it and return true */ private void paintStatus(final Graphics2D g) { if (m_status.isOK()) return; final String message = m_status.getMessage(); final int stringWidth = g.getFontMetrics().stringWidth(message); final int width = getWidth(); final int height = getHeight(); g.setColor(m_backgroundColor); g.fillRect(0, 0, width, height); g.setColor(Color.black); g.drawString(message, (width - stringWidth) / 2, height / 2); } /** * Removes this listener from the mapPanel. */ @Override public void removeMapPanelListener(final IMapPanelListener l) { m_mapPanelListeners.remove(l); } @Override public void removePaintListener(final IMapPanelPaintListener pl) { m_paintListeners.remove(pl); } @Override public void removeSelectionChangedListener(final ISelectionChangedListener listener) { m_selectionListeners.remove(listener); } /** * This function sets the bounding box to this map panel and all its themes. * * @param wishBBox * The new extent, will be adapted so it fits into the current size of the panel. */ @Override public void setBoundingBox(final GM_Envelope wishBBox) { setBoundingBox(wishBBox, true); } @Override public void setBoundingBox(final GM_Envelope wishBBox, final boolean useHistory) { setBoundingBox(wishBBox, useHistory, true); } @Override public void setBoundingBox(final GM_Envelope wishBBox, final boolean useHistory, final boolean invalidateMap) { final GM_Envelope oldExtent; synchronized (this) { oldExtent = m_boundingBox; /* The wished bounding box. */ m_wishBBox = wishBBox; /* We do remember the wish-box here, this behaves more nicely if the size of the view changed meanwhile. */ if (useHistory && m_wishBBox != null) m_extentHistory.push(m_wishBBox); m_boundingBox = determineBoundingBox(); } if (invalidateMap) { /* Tell everyone, that the extent has changed. */ fireExtentChanged(oldExtent, m_boundingBox); invalidateMap(); } else repaintMap(); } /** * Determines the real bounding box from the current wish box.<br> * If the wish box is currently null, we always maximize the map. This is specially nice for previously empty maps, * and a new theme is added. */ private GM_Envelope determineBoundingBox() { /* Adjust the new extent (using the wish bounding box). */ final double ratio = MapPanelUtilities.getRatio(this); // FIXME: if m_wishBox == null at this point, full extent will be called at this point, which is very slow. // Probably we should put this in a separate job final GM_Envelope boundingBox = MapModellHelper.adjustBoundingBox(m_model, m_wishBBox, ratio); if (boundingBox != null) { KalypsoCoreDebug.MAP_PANEL.printf("MinX: %d%n", boundingBox.getMin().getX()); //$NON-NLS-1$ KalypsoCoreDebug.MAP_PANEL.printf("MinY: %d%n", boundingBox.getMin().getY()); //$NON-NLS-1$ KalypsoCoreDebug.MAP_PANEL.printf("MaxX: %d%n", boundingBox.getMax().getX()); //$NON-NLS-1$ KalypsoCoreDebug.MAP_PANEL.printf("MaxY: %d%n", boundingBox.getMax().getY()); //$NON-NLS-1$ } return boundingBox; } @Override public void setMapModell(final IKalypsoLayerModell modell) { final IKalypsoLayerModell oldModel; IMapLayer[] oldLayers = new IMapLayer[0]; synchronized (this) { oldModel = m_model; if (oldModel != null) { oldModel.removeMapModelListener(m_modellListener); oldLayers = m_layers.values().toArray(new IMapLayer[m_layers.values().size()]); m_layers.clear(); } m_model = modell; } // BUGFIX: dispose layers outside of sync block in order to avoid dead lock for (final IMapLayer layer : oldLayers) layer.dispose(); if (modell != null) modell.addMapModelListener(m_modellListener); invalidateMap(); updateStatus(); fireMapModelChanged(oldModel, modell); } protected void updateStatus() { if (m_model == null) setStatus(new Status(IStatus.INFO, KalypsoCorePlugin.getID(), Messages.getString("org.kalypso.ogc.gml.map.MapPanel.20"), null)); //$NON-NLS-1$ else { // We should instead get a status from the model itself if (m_model.getThemeSize() == 0) setStatus(new Status(IStatus.INFO, KalypsoCorePlugin.getID(), Messages.getString("org.kalypso.ogc.gml.map.MapPanel.21"), null)); //$NON-NLS-1$ else setStatus(Status.OK_STATUS); } } /** * Sets the message of this mapPanel. Some widgets update it, so that the MapView could update the status-bar text. */ @Override public void setMessage(final String message) { m_message = message; fireMessageChanged(message); } @Override public void setSelection(final ISelection selection) { if (selection instanceof IFeatureSelection) { final IFeatureSelection featureSelection = (IFeatureSelection) selection; final EasyFeatureWrapper[] allFeatures = featureSelection.getAllFeatures(); getSelectionManager().setSelection(allFeatures); } } @Override public void update(final Graphics g) { // do not clear background, it flickers even if we double buffer paint(g); } @Override public void fireMouseMouveEvent(final int mousex, final int mousey) { final IMapModell mapModell = getMapModell(); if (mapModell == null) return; final GeoTransform transform = getProjection(); if (transform == null) return; final double gx = transform.getSourceX(mousex); final double gy = transform.getSourceY(mousey); final String cs = mapModell.getCoordinatesSystem(); final GM_Point gmPoint = GeometryFactory.createGM_Point(gx, gy, cs); final IMapPanelListener[] listeners = m_mapPanelListeners.toArray(new IMapPanelListener[] {}); for (final IMapPanelListener mpl : listeners) mpl.onMouseMoveEvent(this, gmPoint, mousex, mousey); } @Override public GeoTransform getProjection() { final GM_Envelope boundingBox = m_boundingBox; if (boundingBox == null) return null; final GeoTransform projection = new WorldToScreenTransform(); projection.setSourceRect(boundingBox); final int width = getWidth(); final int height = getHeight(); projection.setDestRect(0, 0, width, height, null); return projection; } @Override public ExtentHistory getExtentHistory() { return m_extentHistory; } private void setStatus(final IStatus status) { if (StatusUtilities.equals(m_status, status)) return; m_status = status; fireStatusChanged(); } @Override public IStatus getStatus() { return m_status; } @Override public void repaintMap() { repaint(50); } @Override public BufferedImage getMapImage() { final BufferPaintJob bufferPaintJob = m_bufferPaintJob; // get copy for thread safety if (bufferPaintJob == null) return null; return bufferPaintJob.getImage(); } /** * Create the list of layers in the order it should be rendered. */ private IMapLayer[] getLayersForRendering() { final List<IMapLayer> result = new ArrayList<>(20); final List<IKalypsoFeatureTheme> visibleFestureThemes = new ArrayList<>(10); final IKalypsoThemeVisitor createLayerVisitor = new IKalypsoThemeVisitor() { @Override public boolean visit(final IKalypsoTheme theme) { if (theme.isVisible()) { final IMapLayer layer = getLayer(theme); result.add(layer); if (theme instanceof IKalypsoFeatureTheme) visibleFestureThemes.add((IKalypsoFeatureTheme) theme); return true; } // No sense in descending into invisible cascading-themes return false; } }; m_model.accept(createLayerVisitor, IKalypsoThemeVisitor.DEPTH_INFINITE); // Reverse list, last should be rendered first. Collections.reverse(result); final IKalypsoFeatureTheme[] selectionThemes = visibleFestureThemes .toArray(new IKalypsoFeatureTheme[visibleFestureThemes.size()]); /* Paint selection in same order as all themes */ ArrayUtils.reverse(selectionThemes); // TODO: care for disposal of SelectionMapLayer result.add(new SelectionMapLayer(this, selectionThemes)); result.add(new WidgetLayer(this, m_widgetManager)); return result.toArray(new IMapLayer[result.size()]); } protected IMapLayer getLayer(final IKalypsoTheme theme) { synchronized (this) { final IMapLayer existingLayer = m_layers.get(theme); if (existingLayer != null) return existingLayer; // TODO: move into factory method; there should be an extension-point... final IMapLayer newLayer; if (theme instanceof IKalypsoCascadingTheme) newLayer = new CacscadingMapLayer(this, theme); else if (theme instanceof IKalypsoFeatureTheme) { // REMARK: un-comment to change to different rendering strategy. I like // 'BufferedRescale' best... // newLayer = new DirectMapLayer( this, theme ); // newLayer = new BufferedMapLayer( this, theme ); // Render asynchronous: no // Repaint during rendering: yes newLayer = new BufferedRescaleMapLayer(this, theme, new MutexRule(), true, m_layerImageCache, LAYER_REPAINT_MILLIS); } else if (theme.getClass().getName().endsWith("KalypsoWMSTheme")) //$NON-NLS-1$ { // Render asynchronously: yes (own mutex) // Repaint during rendering: no newLayer = new BufferedRescaleMapLayer(this, theme, new MutexRule(), false, m_layerImageCache); } else if (theme.getClass().getName().endsWith("KalypsoScaleTheme")) //$NON-NLS-1$ newLayer = new DirectMapLayer(this, theme); else if (theme.getClass().getName().endsWith("KalypsoLegendTheme")) //$NON-NLS-1$ newLayer = new DirectMapLayer(this, theme); else { // Render asynchronous: no // Repaint during rendering: no newLayer = new BufferedRescaleMapLayer(this, theme, m_layerMutex, false, m_layerImageCache); } m_layers.put(theme, newLayer); return newLayer; } } protected void handleThemeRemoved(final IKalypsoTheme theme, final boolean lastVisibility) { final IMapLayer layer = m_layers.remove(theme); if (layer != null) layer.dispose(); if (lastVisibility) setBoundingBox(m_wishBBox, false, true); updateStatus(); } @Override public boolean isMultitouchEnabled() { return m_isMultitouchEnabled; } public void setMTObject(final IDisposable mtApp) { m_mtObject = mtApp; } @Override public Object getMTObject() { return m_mtObject; } void checkBoundingBox() { if (m_boundingBox == null) setBoundingBox(m_wishBBox, false, false); } /** * Lets the active widget paint itself. */ private void paintWidget(final Graphics g) { g.setColor(Color.RED); m_widgetManager.paintWidget(g); } }