/******************************************************************************* * Copyright (c) 2012 Hallvard Trætteberg. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Hallvard Trætteberg - initial API and implementation ******************************************************************************/ package org.eclipse.nebula.widgets.geomap.jface; import org.eclipse.jface.viewers.ContentViewer; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IContentProvider; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.window.DefaultToolTip; import org.eclipse.jface.window.ToolTip; import org.eclipse.nebula.widgets.geomap.GeoMap; import org.eclipse.nebula.widgets.geomap.GeoMapUtil; import org.eclipse.nebula.widgets.geomap.PointD; import org.eclipse.nebula.widgets.geomap.internal.DefaultMouseHandler; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; /** * A JFace viewer for the GeoMap widget, that shows the geo-location of the input elements. * The LocationProvider maps the input elements to geo-locations * @author hal * */ public class GeoMapViewer extends ContentViewer { private GeoMap geoMap; private LocationProvider locationProvider; private Object selection = null; private Point selectionOffset = null; private DefaultMouseHandler mouseHandler; /** * Creates a GeoMapViewer for a specific GeoMap * @param geoMap the GeoMap */ public GeoMapViewer(GeoMap geoMap) { this.geoMap = geoMap; hookControl(geoMap); this.geoMap.removeMouseHandler(this.geoMap.getDefaultMouseHandler()); this.geoMap.addMouseHandler(mouseHandler = new MovePinMouseHandler(this)); geoMap.addPaintListener(new PaintListener() { public void paintControl(PaintEvent e) { paintOverlay(e); } }); geoMap.addKeyListener(new KeyAdapter() { public void keyReleased(KeyEvent e) { switch (e.keyCode) { case SWT.ESC: revealAll(); break; } } }); } /** * Creates a GeoMapViewer with a default GeoMap inside a specific Composite * @param parent the parent Composite * @param flags the SWT options */ public GeoMapViewer(Composite parent, int flags) { this(new GeoMap(parent, flags)); } @Override protected void handleDispose(DisposeEvent event) { this.geoMap.removeMouseHandler(mouseHandler); if (lastToolTip != null) { lastToolTip.deactivate(); lastToolTip = null; } if (toolTip != null) { toolTip.deactivate(); toolTip = null; } super.handleDispose(event); } @Override protected void handleLabelProviderChanged(LabelProviderChangedEvent event) { super.handleLabelProviderChanged(event); refresh(); } /** * Returns the location provider for this GeoMapViewer. * @return the location provider */ public LocationProvider getLocationProvider() { return locationProvider; } /** * Sets the location provider for this GeoMapViewer. The location provider determines where the icon for each element is placed. * @param locationProvider the location provider */ public void setLocationProvider(LocationProvider locationProvider) { this.locationProvider = locationProvider; } // private void paintOverlay(PaintEvent e) { doContents(e.gc, null, selection); } private Object[] getElements() { IContentProvider contentProvider = getContentProvider(); return contentProvider instanceof IStructuredContentProvider ? ((IStructuredContentProvider) contentProvider).getElements(getInput()) : null; } private Object doContents(GC gc, Rectangle contain, Object selection) { Object[] elements = getElements(); if (elements != null && getLocationProvider() != null) { for (int i = 0; i < elements.length; i++) { Object element = elements[i]; Object found = doContent(element, gc, contain, selection); if (found != null) { return found; } } } return null; } /** * Gets the element found within the rectangle of the given position and size * @param x the x coordinate of center of the rectangle * @param y the y coordinate of center of the rectangle * @param thumbSize the width and height of the rectangle * @return the element found or null, if none where found */ public Object getElementAt(int x, int y, int thumbSize) { return doContents(null, new Rectangle(x - thumbSize / 2, y - thumbSize / 2, thumbSize, thumbSize), null); } /** * Constant indicating that location markers should not be clipped */ public static int NO_CLIP = 0; /** * Constant indicating the location markers should be clipped, based on their position */ public static int CLIP_ON_ELEMENT_POSITION = 1; /** * Constant indicating the location markers should be clipped, based on the bounding box of their image */ public static int CLIP_ON_IMAGE_BOUNDS = 2; private int clipRule = CLIP_ON_ELEMENT_POSITION; /** * Sets the clipping rule * @param clipRule the clipping rule, one of the constants NO_CLIP, CLIP_ON_ELEMENT_POSITION or CLIP_ON_IMAGE_BOUNDS */ public void setClipRule(int clipRule) { this.clipRule = clipRule; } private Object doContent(Object element, GC gc, Rectangle contain, Object selection) { Point p = getElementPosition(element, null, true); if (p == null) { return null; } if (gc != null && clipRule == CLIP_ON_ELEMENT_POSITION) { if (p.x < 0 || p.y < 0) { return null; } Point size = geoMap.getSize(); if (p.x > size.x || p.y > size.y) { return null; } } IBaseLabelProvider labelProvider = getLabelProvider(); Image image = null; if (labelProvider instanceof ILabelProvider) { @SuppressWarnings("unused") String text = ((ILabelProvider) labelProvider).getText(element); image = ((ILabelProvider) labelProvider).getImage(element); } if (image == null) { return null; } Rectangle bounds = image.getBounds(); bounds.x = p.x; bounds.y = p.y; if (labelProvider instanceof IPinPointProvider) { Point pinPoint = ((IPinPointProvider) labelProvider).getPinPoint(element); if (pinPoint != null) { bounds.x -= pinPoint.x; bounds.y -= pinPoint.y; } } if (gc != null) { boolean shouldClip = false; if (clipRule == CLIP_ON_IMAGE_BOUNDS) { Point size = geoMap.getSize(); shouldClip = ! bounds.intersects(0, 0, size.x, size.y); } if (! shouldClip) { boolean isSelected = selection != null && element == selection; if (isSelected && selectionOffset != null) { bounds.x += selectionOffset.x; bounds.y += selectionOffset.y; } gc.drawImage(image, bounds.x, bounds.y); if (isSelected) { gc.drawRectangle(bounds.x, bounds.y, bounds.width, bounds.height); } } } if (contain != null && bounds.contains(contain.x, contain.y) && bounds.contains(contain.x + contain.width, contain.y + contain.height)) { return element; } return null; } private Point getElementPosition(Object element, Point into, boolean mapRelative) { PointD lonLat = getLocationProvider().getLonLat(element); if (lonLat == null) { lonLat = (element instanceof PointD ? (PointD) element : (element instanceof Located ? ((Located) element).getLonLat() : null)); } if (lonLat == null) { return null; } int x = GeoMapUtil.lon2position(lonLat.x, geoMap.getZoom()); int y = GeoMapUtil.lat2position(lonLat.y, geoMap.getZoom()); if (mapRelative) { Point p = geoMap.getMapPosition(); x -= p.x; y -= p.y; } if (into == null) { into = new Point(x, y); } else { into.x = x; into.y = y; } return into; } @Override public Control getControl() { return getGeoMap(); } /** * Returns the underlying GeoMap control * @return the underlying GeoMap control */ public GeoMap getGeoMap() { return geoMap; } @Override public ISelection getSelection() { return (selection != null ? new StructuredSelection(selection) : StructuredSelection.EMPTY); } @Override public void refresh() { geoMap.redraw(); } @Override protected void inputChanged(Object input, Object oldInput) { refresh(); } @Override public void setSelection(ISelection selection, boolean reveal) { setSelection(selection instanceof IStructuredSelection ? ((IStructuredSelection) selection).getFirstElement() : null); if (reveal && this.selection != null) { reveal(this.selection, true); } } private int revealMargin = 10; /** * Pans the viewer so the element is revealed * @param selection the element to reveal * @param center whether to center on the element */ public void reveal(Object selection, boolean center) { Point position = getElementPosition(selection, new Point(0, 0), true); Point size = geoMap.getSize(); Rectangle insideMargin = new Rectangle(revealMargin, revealMargin, size.x - revealMargin, size.y - revealMargin); if (position != null && (center || (! insideMargin.contains(position)))) { Point mapPosition = geoMap.getMapPosition(); geoMap.setCenterPosition(new Point(position.x + mapPosition.x, position.y + mapPosition.y)); } } /** * Pans and zooms so all elements are revealed. */ public void revealAll() { zoomTo(((IStructuredContentProvider) getContentProvider()).getElements(getInput()), -1); } private void setSelection(Object selection) { this.selection = selection; refresh(); fireSelectionChanged(new SelectionChangedEvent(this, new StructuredSelection(selection))); } // /** * Constant indicating the selection cannot be moved, i.e. its location is read-only */ public final static int MOVE_SELECTION_NONE = 0; /** * Constant indicating the selection will be moved immediately, using the LocationProvider's set method */ public final static int MOVE_SELECTION_ALLOW_CHECK_IMMEDIATE = 1; /** * Constant indicating the selection will be moveable, but the actual move is performed using the LocationProvider's set method, when dropping */ public final static int MOVE_SELECTION_ALLOW_CHECK_LATE = 2; private int moveSelectionMode = MOVE_SELECTION_ALLOW_CHECK_IMMEDIATE; /** * Sets the current move mode, one of MOVE_SELECTION_NONE, MOVE_SELECTION_ALLOW_CHECK_IMMEDIATE or MOVE_SELECTION_ALLOW_CHECK_LATE * @param moveSelectionMode */ public void setMoveSelectionMode(int moveSelectionMode) { this.moveSelectionMode = moveSelectionMode; } /** * Gets the current move mode * @return the current move mode */ public int getMoveSelectionMode() { return moveSelectionMode; } private ToolTip toolTip, lastToolTip; /** * Gets the current ToolTip * @return the current ToolTip */ public ToolTip getToolTip() { if (toolTip == null) { toolTip = new DefaultToolTip(getControl()); } return toolTip; } private int thumbSize = 7; private class MovePinMouseHandler extends org.eclipse.nebula.widgets.geomap.internal.DefaultMouseHandler { MovePinMouseHandler(GeoMapViewer geoMapViewer) { super(geoMapViewer.getGeoMap()); } public Point getMapSize() { return getControl().getSize(); } @Override protected boolean isPanStart(MouseEvent e) { return super.isPanStart(e) && getElementAt(e.x, e.y, thumbSize) == null; } private Point selectionStart = null; private boolean isSelecting() { return selectionOffset != null; } public void mouseDown(MouseEvent e) { super.mouseDown(e); if (isPanning() || isZooming()) { return; } Object element = getElementAt(e.x, e.y, thumbSize); if (element != null) { selectionStart = new Point(e.x, e.y); selectionOffset = new Point(0, 0); PointD lonLat = getLocationProvider().getLonLat(element); if (moveSelectionMode == MOVE_SELECTION_NONE || (moveSelectionMode == MOVE_SELECTION_ALLOW_CHECK_IMMEDIATE && (! getLocationProvider().setLonLat(element, lonLat.x, lonLat.y)))) { selectionOffset = null; } setSelection(element); } } @Override public void mouseMove(MouseEvent e) { if (isSelecting()) { selectionOffset.x = e.x - selectionStart.x; selectionOffset.y = e.y - selectionStart.y; refresh(); } else { super.mouseMove(e); } } @Override public void mouseUp(MouseEvent e) { if (isSelecting()) { PointD lonLat = getLocationProvider().getLonLat(selection); int zoom = geoMap.getZoom(); Point position = GeoMapUtil.computePosition(lonLat, zoom); Point newPosition = new Point(position.x + selectionOffset.x, position.y + selectionOffset.y); lonLat = GeoMapUtil.getLongitudeLatitude(newPosition, zoom); @SuppressWarnings("unused") boolean changed = getLocationProvider().setLonLat(selection, lonLat.x, lonLat.y); reveal(selection, checkButtons(e, getPanCenterButtons())); selectionStart = null; selectionOffset = null; } else { super.mouseUp(e); } } public void mouseHover(MouseEvent e) { handleToolTip(e); } } void handleToolTip(Event e) { handleToolTip(e.x, e.y);} void handleToolTip(MouseEvent e) { handleToolTip(e.x, e.y);} private void handleToolTip(int x, int y) { if (getLabelProvider() instanceof IToolTipProvider) { // DefaultToolTip toolTip = (DefaultToolTip) getToolTip(); Object element = getElementAt(x, y, thumbSize); Object toolTip = ((IToolTipProvider) getLabelProvider()).getToolTip(element); if (toolTip instanceof String) { if (getToolTip() instanceof DefaultToolTip) { DefaultToolTip defaultToolTip = (DefaultToolTip) getToolTip(); defaultToolTip.setText((String) toolTip); toolTip = defaultToolTip; } } if (lastToolTip != toolTip) { if (lastToolTip != null) { lastToolTip.deactivate(); } lastToolTip = (ToolTip) toolTip; if (lastToolTip != null) { lastToolTip.activate(); } } } } private Rectangle getBounds(Object[] elements) { Rectangle rect = null; for (int i = 0; i < elements.length; i++) { PointD location = getLocationProvider().getLonLat(elements[i]); if (location == null) { continue; } Point position = GeoMapUtil.computePosition(location, geoMap.getZoom()); if (rect == null) { rect = new Rectangle(position.x, position.y, 1, 1); } else { if (position.x < rect.x) { rect.width += rect.x - position.x; rect.x = position.x; } else if (position.x > rect.x + rect.width) { rect.width = position.x - rect.x; } if (position.y < rect.y) { rect.height += rect.y - position.y; rect.y = position.y; } else if (position.y > rect.y + rect.height) { rect.height = position.y - rect.y; } } } return rect; } private static final int MAX_ZOOM_TO = 12; /** * Zooms out so all elements are revealed, but only upto a certain zoom level. * @param elements the elements to reveal * @param maxZoom the maximum zoom */ public void zoomTo(Object[] elements, int maxZoom) { getGeoMap().setZoom(getGeoMap().getTileServer().getMaxZoom()); Rectangle rect = getBounds(elements); if (rect == null) { return; } GeoMapUtil.zoomTo(getGeoMap(), getGeoMap().getSize(), rect, maxZoom >= 0 ? maxZoom : MAX_ZOOM_TO); } }