/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C)2012 - 2014, 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.BorderLayout;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.measure.Unit;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListCellRenderer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JToggleButton;
import javax.swing.JToolBar;
import javax.swing.OverlayLayout;
import javax.swing.SwingConstants;
import org.geotoolkit.gui.swing.navigator.DateRenderer;
import org.geotoolkit.gui.swing.navigator.DoubleRenderer;
import org.geotoolkit.gui.swing.render2d.JMap2D;
import org.geotoolkit.font.FontAwesomeIcons;
import org.geotoolkit.font.IconBuilder;
import org.geotoolkit.gui.swing.util.JOptionDialog;
import org.geotoolkit.gui.swing.util.JTabHeader;
import org.geotoolkit.map.FeatureMapLayer;
import org.geotoolkit.map.FeatureMapLayer.DimensionDef;
import org.geotoolkit.map.MapItem;
import org.geotoolkit.map.MapLayer;
import org.apache.sis.referencing.CRS;
import org.jdesktop.swingx.combobox.ListComboBoxModel;
import org.opengis.geometry.Envelope;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.crs.SingleCRS;
import org.opengis.referencing.crs.TemporalCRS;
import org.opengis.referencing.cs.AxisDirection;
import org.opengis.referencing.cs.CoordinateSystemAxis;
import org.opengis.referencing.operation.TransformException;
import org.apache.sis.referencing.CommonCRS;
import org.apache.sis.util.Utilities;
/**
* Component that allows to browse data on an additional axis.
*
* @author Johann Sorel (Geomatys)
*/
public class JAdditionalAxisNavigator extends JPanel {
private static final ImageIcon ICON_ADD = IconBuilder.createIcon(FontAwesomeIcons.ICON_PLUS_CIRCLE, 16, FontAwesomeIcons.DEFAULT_COLOR);
private static final ImageIcon ICON_SEPARATE = IconBuilder.createIcon(FontAwesomeIcons.ICON_LIST_OL, 16, FontAwesomeIcons.DEFAULT_COLOR);
private static final ImageIcon ICON_VERT = IconBuilder.createIcon(FontAwesomeIcons.ICON_LEVEL_UP, 16, FontAwesomeIcons.DEFAULT_COLOR);
private static final ImageIcon ICON_DELETE = IconBuilder.createIcon(FontAwesomeIcons.ICON_MINUS_CIRCLE, 14, FontAwesomeIcons.DEFAULT_COLOR);
private class AxisDef{
private final CoordinateReferenceSystem axis;
private final ActionListener closeAction = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
removeAxis(AxisDef.this);
}
};
private JMapAxisLine nav;
public AxisDef(final CoordinateReferenceSystem axis){
this.axis = axis;
nav = new JMapAxisLine(axis);
if(axis instanceof TemporalCRS){
nav.setModelRenderer(new DateRenderer());
long now = System.currentTimeMillis();
nav.getModel().translate(-now);
nav.getModel().scale(0.0000001d, 0);
}else{
nav.setModelRenderer(new DoubleRenderer());
}
}
private String getAxisShortName(){
return axis.getName().getCode();
}
private JPanel buildButton(boolean vertical){
final JPanel panel = new JPanel(new BorderLayout());
panel.setOpaque(false);
final JButton guiDel = new JButton("",ICON_DELETE);
guiDel.setMargin(new Insets(0, 0, 0, 0));
guiDel.setVerticalTextPosition(SwingConstants.BOTTOM);
guiDel.addActionListener(closeAction);
final JLabel lbl = new JLabel();
String text = getAxisShortName();
if(vertical){
final StringBuilder sb = new StringBuilder();
sb.append("<html>");
for(int i=0;i<text.length();i++){
sb.append(text.charAt(i));
sb.append("<br/>");
}
sb.append("</html>");
text = sb.toString();
}
lbl.setText(text);
if(vertical){
panel.add(BorderLayout.CENTER,lbl);
panel.add(BorderLayout.NORTH,guiDel);
}else{
panel.add(BorderLayout.CENTER,lbl);
panel.add(BorderLayout.WEST,guiDel);
}
return panel;
}
}
private final List<AxisDef> axis = new ArrayList<>();
private final JToolBar toolbar = new JToolBar(JToolBar.VERTICAL);
private final JPanel content = new JPanel();
private JTabbedPane tabPane = new JTabbedPane(JTabbedPane.RIGHT);
private final JList guiAxis = new JList();
private volatile JMap2D map = null;
private final Action addButton = new AbstractAction("", ICON_ADD) {
@Override
public void actionPerformed(ActionEvent e) {
showSelectAxisPane();
}
};
private final JToggleButton separateAction = new JToggleButton(new AbstractAction("", ICON_SEPARATE) {
@Override
public void actionPerformed(ActionEvent e) {
updateLayout();
}
});
private final JToggleButton verticalAction = new JToggleButton(new AbstractAction("", ICON_VERT) {
@Override
public void actionPerformed(ActionEvent e) {
updateLayout();
}
});
public JAdditionalAxisNavigator(){
super(new BorderLayout());
toolbar.setFloatable(false);
toolbar.add(addButton);
toolbar.add(separateAction);
toolbar.add(verticalAction);
guiAxis.setCellRenderer(new DefaultListCellRenderer(){
@Override
public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
final Object tmpVal = value;
if (value instanceof CoordinateReferenceSystem) {
value = "";
}
final JLabel lbl = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
if(tmpVal instanceof CoordinateReferenceSystem){
final CoordinateReferenceSystem crs = (CoordinateReferenceSystem) tmpVal;
final StringBuilder sb = new StringBuilder();
final CoordinateSystemAxis axi = crs.getCoordinateSystem().getAxis(0);
if(axi != null){
if(axi.getDirection() != null && axi.getDirection().identifier() != null){
sb.append(axi.getDirection().identifier().toUpperCase());
}
final Unit unit = axi.getUnit();
if(unit != null){
sb.append(" (");
sb.append(unit);
sb.append(") ");
}
sb.append(crs.getName().getCode());
}
lbl.setText(sb.toString());
}
if(tmpVal instanceof CoordinateSystemAxis){
final CoordinateSystemAxis axis = (CoordinateSystemAxis) tmpVal;
lbl.setText(axis.getName().getCode());
}
return lbl;
}
});
add(BorderLayout.WEST,toolbar);
add(BorderLayout.CENTER,content);
}
public JToolBar getToolbar() {
return toolbar;
}
private void updateModel() {
final List<CoordinateReferenceSystem> values = new ArrayList<>();
values.add(CommonCRS.Vertical.ELLIPSOIDAL.crs());
values.add(CommonCRS.Temporal.JAVA.crs());
if (map != null) {
getDimensions(map.getContainer().getContext(), values);
}
guiAxis.setModel(new ListComboBoxModel(values));
}
private void updateLayout(){
content.removeAll();
if(separateAction.isSelected()){
if(verticalAction.isSelected()){
content.setLayout(new GridLayout(1,0));
for (AxisDef def : axis) {
def.nav.setOrientation(verticalAction.isSelected() ? SwingConstants.WEST : SwingConstants.SOUTH);
//change model scale if needed
final double scale = def.nav.getModel().getScale();
if(scale>0){
//scale is going up, must be negative
def.nav.getModel().scale(-1, 0);
}
final JPanel over = new JPanel(new FlowLayout(FlowLayout.RIGHT));
over.setOpaque(false);
over.add(def.buildButton(verticalAction.isSelected()));
final JPanel stack = new JPanel(){
@Override
public boolean isOptimizedDrawingEnabled() {
return false;
}
};
stack.setLayout(new OverlayLayout(stack));
stack.add(over);
stack.add(def.nav);
content.add(stack);
}
}else{
content.setLayout(new GridLayout(0,1));
for (AxisDef def : axis) {
def.nav.setOrientation(verticalAction.isSelected() ? SwingConstants.WEST : SwingConstants.SOUTH);
//change model scale if needed
final double scale = def.nav.getModel().getScale();
if(scale<0){
//scale is going down, must be negative
def.nav.getModel().scale(-1, 0);
}
final JPanel over = new JPanel(new FlowLayout(FlowLayout.LEFT));
over.setOpaque(false);
over.add(def.buildButton(verticalAction.isSelected()));
final JPanel stack = new JPanel(){
@Override
public boolean isOptimizedDrawingEnabled() {
return false;
}
};
stack.setLayout(new OverlayLayout(stack));
stack.add(over);
stack.add(def.nav);
content.add(stack);
}
}
}else{
content.setLayout(new BorderLayout());
//create a tab view
if(tabPane != null){
tabPane.removeAll();
}
if(verticalAction.isSelected()){
tabPane = new JTabbedPane(JTabbedPane.TOP);
}else{
tabPane = new JTabbedPane(JTabbedPane.LEFT);
}
content.add(BorderLayout.CENTER,tabPane);
for(int i=0;i<axis.size();i++) {
final AxisDef def = axis.get(i);
def.nav.setOrientation(verticalAction.isSelected() ? SwingConstants.WEST : SwingConstants.SOUTH);
final double scale = def.nav.getModel().getScale();
if((verticalAction.isSelected() && scale>0) || (!verticalAction.isSelected() && scale<0)){
//scale must be inverted
def.nav.getModel().scale(-1, 0);
}
tabPane.addTab(def.getAxisShortName(), def.nav);
tabPane.setTabComponentAt(i, new JTabHeader(tabPane,def.closeAction));
}
}
content.revalidate();
content.repaint();
}
private static void getDimensions(final MapItem source, final List<CoordinateReferenceSystem> toFill) {
if (source == null) {
return;
}
if (source instanceof MapLayer) {
final MapLayer layer = (MapLayer) source;
if (layer.isVisible() || layer.isSelectable()) {
final Envelope bounds = layer.getBounds();
if (bounds != null && bounds.getCoordinateReferenceSystem() != null) {
final CoordinateReferenceSystem layerCRS = bounds.getCoordinateReferenceSystem();
final List<SingleCRS> parts = CRS.getSingleComponents(layerCRS);
browseCRS: for (CoordinateReferenceSystem part : parts) {
if (part.getCoordinateSystem().getDimension() == 1
&& part.getCoordinateSystem().getAxis(0).getDirection() != AxisDirection.FUTURE) {
// Check we haven't already got it
for (CoordinateReferenceSystem inserted : toFill) {
if (Utilities.equalsApproximatively(inserted, part)) {
continue browseCRS;
}
}
toFill.add(part);
}
}
}
// Handle extra dimensions of feature map layer
if (layer instanceof FeatureMapLayer) {
final FeatureMapLayer fml = (FeatureMapLayer) layer;
for (final DimensionDef extraDim : fml.getExtraDimensions()) {
if (extraDim.getCrs() != null) {
toFill.add(extraDim.getCrs());
}
}
}
}
} else {
for (MapItem item : source.items()) {
getDimensions(item, toFill);
}
}
}
public JMap2D getMap() {
return map;
}
public void setMap(final JMap2D map) {
this.map = map;
for(AxisDef def : axis){
def.nav.setMap(map);
}
repaint();
}
private void showSelectAxisPane(){
updateModel();
final int result = JOptionDialog.show(this, new JScrollPane(guiAxis), JOptionPane.OK_CANCEL_OPTION);
if(result == JOptionPane.OK_OPTION){
addAxis();
}
}
private void addAxis(){
final Object obj = guiAxis.getSelectedValue();
final CoordinateReferenceSystem axi = (CoordinateReferenceSystem) obj;
if(axi == null) return;
//check the axis is not already in the list
for (AxisDef tmp : axis) {
if (Utilities.equalsIgnoreMetadata(guiAxis.getSelectedValue(), tmp.nav.getCrs())) {
return;
}
}
final AxisDef def = new AxisDef(axi);
def.nav.setMap(map);
axis.add(def);
updateLayout();
}
private void removeAxis(AxisDef def){
if(map != null){
try {
map.getCanvas().setAxisRange(null, null, def.nav.getAxisIndexFinder(), def.nav.getCrs());
} catch (TransformException ex) {
ex.printStackTrace();
}
}
def.nav.setMap(null);
axis.remove(def);
updateLayout();
}
}