/* * Geotoolkit - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C)2010-2011, Geomatys * * 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 of the License. * * 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. */ package org.geotoolkit.gui.swing.render2d.control; import java.awt.BasicStroke; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.MouseInfo; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.AbstractAction; import javax.swing.JLabel; import javax.swing.JMenuItem; import javax.swing.JPanel; import javax.swing.JPopupMenu; import javax.swing.JSpinner; import javax.swing.SpinnerNumberModel; import javax.swing.SwingConstants; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.apache.sis.measure.Range; import org.geotoolkit.gui.swing.render2d.JMap2D; import org.geotoolkit.gui.swing.navigator.DoubleRenderer; import org.geotoolkit.gui.swing.navigator.JNavigator; import org.geotoolkit.gui.swing.navigator.JNavigatorBand; import org.geotoolkit.gui.swing.resource.MessageBundle; import org.apache.sis.util.logging.Logging; import org.geotoolkit.display.canvas.AbstractCanvas2D; import org.geotoolkit.map.*; import org.geotoolkit.util.collection.CollectionChangeEvent; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.cs.CoordinateSystemAxis; import org.opengis.referencing.operation.TransformException; import static java.awt.event.KeyEvent.*; /** * A {@link JNavigator} to display a scroll bar. It allows the user to browse * the axis of the {@link CoordinateReferenceSystem} given at built. * * @author Johann Sorel (Geomatys) * @author Alexis Manin (Geomatys) * @module */ public class JMapAxisLine extends JNavigator implements PropertyChangeListener, ContextListener { private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.gui.swing.render2d.control"); private static final Color MAIN = new Color(0f,0.3f,0.6f,1f); private static final Color SECOND = new Color(0f,0.3f,0.6f,0.4f); private static final float LIMIT_WIDTH = 1.25f; private final SpinnerNumberModel modelHaut; private final SpinnerNumberModel modelBas; /** * The popup menu to display at right click. Contains several options to * manage movements, animation, etc. */ private final JPopupMenu menu; /** * A Jcomponent to configure animation mecanism on the current axis. */ private final JAnimationMenu animation = new JAnimationMenu() { @Override protected void update(JMap2D map, double step) { final Double[] range = map.getCanvas().getAxisRange(axisIndexFinder).clone(); if(range[0] != null){ range[0] = range[0] + step; } if(range[1] != null){ range[1] = range[1] + step; } try{ map.getCanvas().setAxisRange(range[0], range[1], axisIndexFinder, crs); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } } }; /** Scrolling mecanism. */ private final ChangeListener spinnerListener = new ChangeListener() { @Override public void stateChanged(ChangeEvent ce) { if(map == null) return; Double vh = (Double) modelHaut.getValue(); Double vb = (Double) modelBas.getValue(); if(vh.isInfinite()) vh = null; if(vb.isInfinite()) vb = null; try{ map.getCanvas().setAxisRange(vb, vh, axisIndexFinder,crs); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } } }; private JLayerBandMenu layers = null; private volatile JMap2D map = null; /** The CRS containing the axis to browse. This CRS should have only one dimension. */ private final CoordinateReferenceSystem crs; private final Comparator<CoordinateSystemAxis> axisIndexFinder; /** * A boolean to determine what mecanism must be used for layer activation. * Two mecanisms are allowed : * - If true, a menu will be added on right click, on which the user will * be able to choose which layer to activate on the current line. * - If false, the choice mecanism will use directly the {@link MapLayer#isSelectable() } * property to determine if should be activated or not. The * {@link MapLayer#isSelectable() } property can be changed via * {@link org.geotoolkit.gui.swing.contexttree.JContextTree} component. */ private final boolean useMenu; public JMapAxisLine(final CoordinateReferenceSystem crs){ this(crs, true); } public JMapAxisLine(final CoordinateReferenceSystem crs, final boolean useMenu) { this.crs = crs; this.axisIndexFinder = new AbstractCanvas2D.AxisFinder(crs.getCoordinateSystem().getAxis(0)); this.useMenu = useMenu; animation.setSpeedFactor(10); setModelRenderer(new DoubleRenderer()); getModel().setCRS(crs); modelHaut = new SpinnerNumberModel(); modelHaut.setStepSize(10); modelHaut.setMinimum(Double.NEGATIVE_INFINITY); modelHaut.setMaximum(Double.POSITIVE_INFINITY); modelHaut.setValue(Double.POSITIVE_INFINITY); modelBas = new SpinnerNumberModel(); modelBas.setStepSize(10); modelBas.setMinimum(Double.NEGATIVE_INFINITY); modelBas.setMaximum(Double.POSITIVE_INFINITY); modelBas.setValue(Double.NEGATIVE_INFINITY); final JSpinner haut = new JSpinner(modelHaut); final JSpinner bas = new JSpinner(modelBas); modelBas.addChangeListener(spinnerListener); modelHaut.addChangeListener(spinnerListener); final JPanel minPan = new JPanel(new BorderLayout()); minPan.add(BorderLayout.WEST, new JLabel("min")); minPan.add(BorderLayout.CENTER, bas); final JPanel maxPan = new JPanel(new BorderLayout()); maxPan.add(BorderLayout.WEST, new JLabel("max")); maxPan.add(BorderLayout.CENTER, haut); menu = new JPopupMenu(){ @Override public void setVisible(boolean b) { if(b){ final Point pt = MouseInfo.getPointerInfo().getLocation(); pt.x -= JMapAxisLine.this.getLocationOnScreen().x; pt.y -= JMapAxisLine.this.getLocationOnScreen().y; final int coord = getCoord(pt); popupEdit = getModel().getDimensionValueAt(coord); } super.setVisible(b); } }; if (useMenu) { layers = new JLayerBandMenu(this); menu.add(layers); } menu.addSeparator(); menu.add(animation); menu.addSeparator(); menu.add(new JMenuItem( new AbstractAction(MessageBundle.format("map_move_elevation_center")) { @Override public void actionPerformed(ActionEvent e) { moveTo(popupEdit); } }){ @Override public boolean isEnabled() { return getMap() != null; } }); menu.add(new JMenuItem( new AbstractAction(MessageBundle.format("map_move_elevation_maximum")) { @Override public void actionPerformed(ActionEvent e) { if(getMap() != null && popupEdit != null){ final AbstractCanvas2D controller = getMap().getCanvas(); final Double[] range = controller.getAxisRange(axisIndexFinder); try{ if(range == null){ controller.setAxisRange(popupEdit, popupEdit, axisIndexFinder, crs); }else{ controller.setAxisRange(range[0],popupEdit, axisIndexFinder,crs); } } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } JMapAxisLine.this.repaint(); } } }){ @Override public boolean isEnabled() { if(getMap() != null){ final AbstractCanvas2D controller = getMap().getCanvas(); final Double[] range = controller.getAxisRange(axisIndexFinder); return range == null || range[0] == null || (range[0] != null && range[0] < popupEdit); } return false; } }); menu.add(new JMenuItem( new AbstractAction(MessageBundle.format("map_move_elevation_minimum")) { @Override public void actionPerformed(ActionEvent e) { if(getMap() != null && popupEdit != null){ final AbstractCanvas2D controller = getMap().getCanvas(); final Double[] range = controller.getAxisRange(axisIndexFinder); try{ if(range == null){ controller.setAxisRange(popupEdit, popupEdit, axisIndexFinder,crs); }else{ controller.setAxisRange(popupEdit, range[1], axisIndexFinder,crs); } } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } JMapAxisLine.this.repaint(); } } }){ @Override public boolean isEnabled() { if(getMap() != null){ final AbstractCanvas2D controller = getMap().getCanvas(); final Double[] range = controller.getAxisRange(axisIndexFinder); return range == null || range[1] == null || (range[1] != null && range[1] > popupEdit); } return false; } }); menu.addSeparator(); menu.add(new JMenuItem( new AbstractAction(MessageBundle.format("map_remove_elevation")) { @Override public void actionPerformed(ActionEvent e) { if(getMap() != null && popupEdit != null){ final AbstractCanvas2D controller = getMap().getCanvas(); try{ controller.setAxisRange(null, null, axisIndexFinder, crs); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } JMapAxisLine.this.repaint(); } } }){ @Override public boolean isEnabled() { if(getMap() != null){ final AbstractCanvas2D controller = getMap().getCanvas(); final Double[] range = controller.getAxisRange(axisIndexFinder); return range != null; } return false; } }); menu.add(new JMenuItem( new AbstractAction(MessageBundle.format("map_remove_elevation_maximum")) { @Override public void actionPerformed(ActionEvent e) { if(getMap() != null && popupEdit != null){ final AbstractCanvas2D controller = getMap().getCanvas(); final Double[] range = controller.getAxisRange(axisIndexFinder); if(range != null){ range[1] = null; try{ controller.setAxisRange(range[0], range[1], axisIndexFinder, crs); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } } JMapAxisLine.this.repaint(); } } }){ @Override public boolean isEnabled() { if(getMap() != null){ final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); return range != null && range[0] != null; } return false; } }); menu.add(new JMenuItem( new AbstractAction(MessageBundle.format("map_remove_elevation_minimum")) { @Override public void actionPerformed(ActionEvent e) { if(getMap() != null && popupEdit != null){ final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); if(range != null){ range[0] = null; try{ getMap().getCanvas().setAxisRange(range[0], range[1], axisIndexFinder, crs); } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } } JMapAxisLine.this.repaint(); } } }){ @Override public boolean isEnabled() { if(getMap() != null){ final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); return range != null && range[0] != null; } return false; } }); menu.addSeparator(); menu.add(minPan); menu.add(maxPan); setComponentPopupMenu(menu); } public CoordinateReferenceSystem getCrs() { return crs; } public Comparator<CoordinateSystemAxis> getAxisIndexFinder() { return axisIndexFinder; } /** * Disable spinner the time to update their values, otherwise * the listener will cause the canvas to be repainted. */ private synchronized void updateSpiners(final double min, final double max){ modelBas.removeChangeListener(spinnerListener); modelHaut.removeChangeListener(spinnerListener); modelBas.setValue(min); modelHaut.setValue(max); modelBas.addChangeListener(spinnerListener); modelHaut.addChangeListener(spinnerListener); } public JMap2D getMap() { return map; } public void setMap(final JMap2D map) { if(this.map != null){ this.map.getCanvas().removePropertyChangeListener(this); } this.map = map; animation.setMap(map); if(map != null){ this.map.getCanvas().addPropertyChangeListener(this); if (useMenu) { layers.setMap(map); } else { final MapContext context = this.map.getContainer().getContext(); if (context != null) { context.addContextListener(this); checkLayerBands(context, CollectionChangeEvent.ITEM_ADDED); } } } repaint(); } /** * Get value checking orientation. * * @param me * @return */ private int getCoord(Point me){ final int coord; final int orientation = getOrientation(); if(orientation == SwingConstants.SOUTH || orientation == SwingConstants.NORTH){ coord = (int)me.getX(); }else if(orientation == SwingConstants.EAST || orientation == SwingConstants.WEST){ coord = (int)me.getY(); }else{ throw new IllegalArgumentException("Invalid orientation : "+orientation); } return coord; } //handle mouse event for dragging range ends ------------------------------- // 0 for left limit // 1 for middle // 2 for right limit private int selected = -1; private Double edit = null; private volatile Double popupEdit = null; @Override public void mousePressed(final MouseEvent e) { if(map != null){ final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); if(range != null){ final int coord = getCoord(e.getPoint()); if(range[0] != null){ int pos = (int)getModel().getGraphicValueAt(range[0]); if( Math.abs(coord-pos) < LIMIT_WIDTH*2 ){ selected = 0; } } if(range[1] != null){ int pos = (int)getModel().getGraphicValueAt(range[1]); if( Math.abs(coord-pos) < LIMIT_WIDTH*2 ){ selected = 2; } } if(range[0] != null && range[1] != null){ int pos = (int) (( getModel().getGraphicValueAt(range[0]) + getModel().getGraphicValueAt(range[1]) ) / 2); if( Math.abs(coord-pos) < LIMIT_WIDTH*4 ){ selected = 1; } } } } super.mousePressed(e); } @Override public void mouseReleased(final MouseEvent e) { if(selected >= 0 && edit != null){ final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); try{ if(selected == 0){ getMap().getCanvas().setAxisRange(edit, range[1], axisIndexFinder, crs); }else if(selected == 2){ getMap().getCanvas().setAxisRange(range[0], edit, axisIndexFinder, crs); }else if(selected == 1){ double middle = (range[0] + range[1]) / 2d; double step = edit - middle; double start = range[0] + step; double end = range[1] + step; getMap().getCanvas().setAxisRange(start, end, axisIndexFinder, crs); } } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } repaint(); } selected = -1; edit = null; super.mouseReleased(e); } @Override public void mouseDragged(final MouseEvent e) { if(selected >= 0){ //drag one limit final int coord = getCoord(e.getPoint()); edit = getModel().getDimensionValueAt(coord); //ensure we do not go over the other limit final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); if(selected == 0 && range[1] != null){ if(edit > range[1]) edit = range[1]; }else if(selected == 2 && range[0] != null){ if(edit < range[0]) edit = range[0]; } repaint(); }else{ super.mouseDragged(e); } } /** * Change behavior of key pressed events to navigate from one element to the next. * @param e */ @Override public void keyPressed(KeyEvent e) { final int code = e.getKeyCode(); boolean next = false; boolean previous = false; switch(code){ case VK_UP : case VK_RIGHT :next=true;break; case VK_LEFT : case VK_DOWN :previous=true;break; } Double[] current = getCurrentRange(); if(current != null && (next || previous)){ //find all elements final SortedSet<Double> steps = new TreeSet<Double>(); final List<JNavigatorBand> bands = getBands(); for(JNavigatorBand band : bands){ final JLayerBand lb = (JLayerBand) band; final List<Range<Double>> ranges = lb.getRanges(); final List<Double> ponctuals = lb.getPonctuals(); if(ranges != null){ for(Range<Double> range :ranges){ steps.add(range.getMinValue()); steps.add(range.getMaxValue()); } } if(ponctuals != null){ steps.addAll(ponctuals); } } final Double[] array = steps.toArray(new Double[0]); boolean range; double middle; if(current[0] == null || Double.isInfinite(current[0]) ){ range = false; middle = current[1]; }else if(current[1] == null || Double.isInfinite(current[1]) ){ range = false; middle = current[0]; }else{ middle = (current[1] + current[0]) / 2.0; } int index = Arrays.binarySearch(array, ((Double) middle)); if(index < 0){ //(-(insertion point) - 1) index = (-(index))-1; if(previous){ index--; } }else{ if(next){ //move the closest element above current value index++; }else if(previous){ index--; } } if(index<0) index = 0; if(index>=array.length) index = array.length-1; moveTo(array[index]); } } @Override public void propertyChange(final PropertyChangeEvent evt) { if(evt.getPropertyName().equals(AbstractCanvas2D.ENVELOPE_KEY)){ Double[] range = map.getCanvas().getAxisRange(axisIndexFinder); if(range == null){ range = new Double[2]; range[0] = Double.NEGATIVE_INFINITY; range[1] = Double.POSITIVE_INFINITY; }else{ if(range[0] == null){ range[0] = Double.NEGATIVE_INFINITY; } if(range[1] == null){ range[1] = Double.POSITIVE_INFINITY; } } updateSpiners(range[0], range[1]); repaint(); } else if (evt.getSource() instanceof MapLayer && !useMenu) { checkLayerBands((MapItem) evt.getSource(), CollectionChangeEvent.ITEM_CHANGED); } } void moveTo(final Double targetValue) { if (getMap() != null && targetValue != null) { final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); try{ if (range == null || range[0] == null || range[1] == null) { getMap().getCanvas().setAxisRange(targetValue, targetValue, axisIndexFinder, crs); } else { double middle = (range[0] + range[1]) / 2l; double step = targetValue - middle; double start = range[0] + step; double end = range[1] + step; getMap().getCanvas().setAxisRange(start, end, axisIndexFinder, crs); } } catch (TransformException ex) { LOGGER.log(Level.WARNING, null, ex); } JMapAxisLine.this.repaint(); } } private Double[] getCurrentRange(){ if (getMap() != null) { final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); return range; } return null; } @Override public void paint(final Graphics g) { super.paint(g); if(map == null) return; final Double[] range = getMap().getCanvas().getAxisRange(axisIndexFinder); if(range == null) return; if(range[0] == null && range[1] == null) return; double start; double end; double center; final int orientation = getOrientation(); if(orientation == SwingConstants.SOUTH || orientation == SwingConstants.NORTH){ start = -5; end = getWidth() +5; center = -5; }else if(orientation == SwingConstants.EAST || orientation == SwingConstants.WEST){ start = getHeight() +5; end = -5; center = -5; }else{ throw new IllegalArgumentException("Invalid orientation : "+orientation); } if(range[0] != null) start = getModel().getGraphicValueAt(range[0]); if(range[1] != null) end = getModel().getGraphicValueAt(range[1]); //apply change if there are some if(edit != null){ if(selected == 0){ start = getModel().getGraphicValueAt(edit); }else if(selected == 2){ end = getModel().getGraphicValueAt(edit); }else if(selected == 1){ double middle = (range[0] + range[1]) / 2l; double step = edit - middle; start = getModel().getGraphicValueAt(range[0] + step); end = getModel().getGraphicValueAt(range[1] + step); } } if(start>end){ double n = start; start = end; end = n; } if(range[0] != null && range[1] != null){ center = (start+end)/2; } final Graphics2D g2d = (Graphics2D) g; if(orientation == SwingConstants.SOUTH || orientation == SwingConstants.NORTH){ g2d.setColor(SECOND); g2d.fillRect((int)start,0,(int)(end-start),getHeight()); g2d.setColor(MAIN); g2d.setStroke(new BasicStroke(LIMIT_WIDTH*2)); g2d.drawLine((int)start,0, (int)start, getHeight()); g2d.drawLine((int)end,0, (int)end, getHeight()); g2d.setStroke(new BasicStroke(LIMIT_WIDTH*4)); g2d.drawLine((int)center,0, (int)center, getHeight()); }else if(orientation == SwingConstants.EAST || orientation == SwingConstants.WEST){ g2d.setColor(SECOND); g2d.fillRect(0,(int)start,getWidth(),(int)(end-start)); g2d.setColor(MAIN); g2d.setStroke(new BasicStroke(LIMIT_WIDTH*2)); g2d.drawLine(0, (int)start, getWidth(), (int)start); g2d.drawLine(0, (int)end, getWidth(), (int)end); g2d.setStroke(new BasicStroke(LIMIT_WIDTH*4)); g2d.drawLine(0, (int)center, getWidth(), (int)center); } } /** * Browse the source MapItem to determine if some {@link JLayerBand} should * be created or removed. * @param source The mapItem on which an event occured. * @param checkType The event type, as described in {@link CollectionChangeEvent} */ private void checkLayerBands(MapItem source, int checkType) { if (source == null) { return; } if (source instanceof MapLayer) { final MapLayer layer = (MapLayer) source; // First, we check if we already get a listener on this object. if (checkType == CollectionChangeEvent.ITEM_ADDED) { final ItemListener.Weak weak = new ItemListener.Weak(this); layer.removeItemListener(weak); layer.addItemListener(weak); } boolean exist = false; for (JNavigatorBand band : getBands()) { if (band instanceof JLayerBand) { final JLayerBand lb = (JLayerBand) band; if (layer.equals(lb.getLayer())) { exist = true; if (checkType == CollectionChangeEvent.ITEM_REMOVED || !layer.isSelectable()) { getBands().remove(lb); } break; } } } if (!exist && layer.isSelectable()) { final JLayerBand band = new JLayerBand(layer, getModel()); if (!band.isEmpty()) { getBands().add(band); } } // If an element have been added or removed, we'll parse item to find // which layer to add or delete. } else if (checkType != CollectionChangeEvent.ITEM_CHANGED) { for (MapItem child : source.items()) { checkLayerBands(child, checkType); } } } @Override public void layerChange(CollectionChangeEvent<MapLayer> event) {} /** * If an item of the current {@link MapContext} changed, we'll check for the * corresponding {@link JLayerBand} of this axis. * @param event */ @Override public void itemChange(CollectionChangeEvent<MapItem> event) { if (!useMenu) { for (MapItem item : event.getItems()) { checkLayerBands(item, event.getType()); } } } }