001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.preferences.projection;
003    
004    import static org.openstreetmap.josm.tools.I18n.tr;
005    
006    import java.awt.BorderLayout;
007    import java.awt.GridBagLayout;
008    import java.awt.Insets;
009    import java.awt.event.ActionEvent;
010    import java.awt.event.ActionListener;
011    import java.util.ArrayList;
012    import java.util.Arrays;
013    import java.util.Collection;
014    import java.util.Collections;
015    import java.util.LinkedList;
016    import java.util.List;
017    import java.util.Map;
018    
019    import javax.swing.JButton;
020    import javax.swing.JComponent;
021    import javax.swing.JLabel;
022    import javax.swing.JPanel;
023    import javax.swing.JTextField;
024    import javax.swing.plaf.basic.BasicComboBoxEditor;
025    
026    import org.openstreetmap.josm.Main;
027    import org.openstreetmap.josm.data.projection.CustomProjection;
028    import org.openstreetmap.josm.data.projection.Projection;
029    import org.openstreetmap.josm.data.projection.ProjectionConfigurationException;
030    import org.openstreetmap.josm.data.projection.Projections;
031    import org.openstreetmap.josm.gui.ExtendedDialog;
032    import org.openstreetmap.josm.gui.tagging.ac.AutoCompletionListItem;
033    import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
034    import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
035    import org.openstreetmap.josm.gui.widgets.HtmlPanel;
036    import org.openstreetmap.josm.tools.GBC;
037    import org.openstreetmap.josm.tools.ImageProvider;
038    import org.openstreetmap.josm.tools.Utils;
039    
040    public class CustomProjectionChoice extends AbstractProjectionChoice implements Alias, SubPrefsOptions {
041    
042        private String pref;
043    
044        public CustomProjectionChoice() {
045            super("core:custom", tr("Custom Projection"));
046        }
047    
048        private static class PreferencePanel extends JPanel {
049    
050            public JTextField input;
051            private HistoryComboBox cbInput;
052    
053            public PreferencePanel(String initialText, ActionListener listener) {
054                build(initialText, listener);
055            }
056    
057            private void build(String initialText, final ActionListener listener) {
058                input = new JTextField(30);
059                cbInput = new HistoryComboBox();
060                cbInput.setPrototypeDisplayValue(new AutoCompletionListItem("xxxx"));
061                cbInput.setEditor(new BasicComboBoxEditor() {
062                    @Override
063                    protected JTextField createEditorComponent() {
064                        return input;
065                    }
066                });
067                Collection<String> samples = Arrays.asList(
068                        "+proj=lonlat +ellps=WGS84 +datum=WGS84 +bounds=-180,-90,180,90",
069                        "+proj=tmerc +lat_0=0 +lon_0=9 +k_0=1 +x_0=3500000 +y_0=0 +ellps=bessel +nadgrids=BETA2007.gsb");
070                List<String> inputHistory = new LinkedList<String>(Main.pref.getCollection("projection.custom.value.history", samples));
071                Collections.reverse(inputHistory);
072                cbInput.setPossibleItems(inputHistory);
073                cbInput.setText(initialText == null ? "" : initialText);
074    
075                final HtmlPanel errorsPanel = new HtmlPanel();
076                errorsPanel.setVisible(false);
077                final JLabel valStatus = new JLabel();
078                valStatus.setVisible(false);
079    
080                final AbstractTextComponentValidator val = new AbstractTextComponentValidator(input, false, false, false) {
081    
082                    private String error;
083    
084                    @Override
085                    public void validate() {
086                        if (!isValid()) {
087                            feedbackInvalid(tr("Invalid projection configuration: {0}",error));
088                        } else {
089                            feedbackValid(tr("Projection configuration is valid."));
090                        }
091                        listener.actionPerformed(null);
092                    }
093    
094                    @Override
095                    public boolean isValid() {
096                        try {
097                            CustomProjection test = new CustomProjection();
098                            test.update(input.getText());
099                        } catch (ProjectionConfigurationException ex) {
100                            error = ex.getMessage();
101                            valStatus.setIcon(ImageProvider.get("data", "error.png"));
102                            valStatus.setVisible(true);
103                            errorsPanel.setText(error);
104                            errorsPanel.setVisible(true);
105                            return false;
106                        }
107                        errorsPanel.setVisible(false);
108                        valStatus.setIcon(ImageProvider.get("misc", "green_check.png"));
109                        valStatus.setVisible(true);
110                        return true;
111                    }
112    
113                };
114    
115                JButton btnCheck = new JButton(tr("Validate"));
116                btnCheck.addActionListener(new ActionListener() {
117                    @Override
118                    public void actionPerformed(ActionEvent e) {
119                        val.validate();
120                    }
121                });
122                btnCheck.setLayout(new BorderLayout());
123                btnCheck.setMargin(new Insets(-1,0,-1,0));
124    
125                JButton btnInfo = new JButton(tr("Parameter information..."));
126                btnInfo.addActionListener(new ActionListener() {
127                    @Override
128                    public void actionPerformed(ActionEvent e) {
129                        CustomProjectionChoice.ParameterInfoDialog dlg = new CustomProjectionChoice.ParameterInfoDialog();
130                        dlg.showDialog();
131                        dlg.toFront();
132                    }
133                });
134    
135                this.setLayout(new GridBagLayout());
136                JPanel p2 = new JPanel(new GridBagLayout());
137                p2.add(cbInput, GBC.std().fill(GBC.HORIZONTAL).insets(0, 20, 5, 5));
138                p2.add(btnCheck, GBC.eol().insets(0, 20, 0, 5));
139                this.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
140                p2 = new JPanel(new GridBagLayout());
141                p2.add(valStatus, GBC.std().anchor(GBC.WEST).weight(0.0001, 0));
142                p2.add(errorsPanel, GBC.eol().fill(GBC.HORIZONTAL));
143                this.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
144                p2 = new JPanel(new GridBagLayout());
145                p2.add(btnInfo, GBC.std().insets(0, 20, 0, 0));
146                p2.add(GBC.glue(1, 0), GBC.eol().fill(GBC.HORIZONTAL));
147                this.add(p2, GBC.eol().fill(GBC.HORIZONTAL));
148            }
149    
150            public void rememberHistory() {
151                cbInput.addCurrentItemToHistory();
152                Main.pref.putCollection("projection.custom.value.history", cbInput.getHistory());
153            }
154        }
155    
156        public static class ParameterInfoDialog extends ExtendedDialog {
157    
158            public ParameterInfoDialog() {
159                super(null, tr("Parameter information"), new String[] { tr("Close") }, false);
160                setContent(build());
161            }
162    
163            private JComponent build() {
164                StringBuilder s = new StringBuilder();
165                s.append("<b>+proj=...</b> - <i>"+tr("Projection name")+"</i><br>");
166                s.append("&nbsp;&nbsp;&nbsp;&nbsp;"+tr("Supported values:")+" ");
167                s.append(listKeys(Projections.projs)+"<br>");
168                s.append("<b>+lat_0=..., +lat_1=..., +lat_2=...</b> - <i>"+tr("Projection parameters")+"</i><br>");
169                s.append("<b>+x_0=..., +y_0=...</b> - <i>"+tr("False easting and false northing")+"</i><br>");
170                s.append("<b>+lon_0=...</b> - <i>"+tr("Central meridian")+"</i><br>");
171                s.append("<b>+k_0=...</b> - <i>"+tr("Scaling factor")+"</i><br>");
172                s.append("<b>+ellps=...</b> - <i>"+tr("Ellipsoid name")+"</i><br>");
173                s.append("&nbsp;&nbsp;&nbsp;&nbsp;"+tr("Supported values:")+" ");
174                s.append(listKeys(Projections.ellipsoids)+"<br>");
175                s.append("<b>+a=..., +b=..., +rf=..., +f=..., +es=...</b> - <i>"+tr("Ellipsoid parameters")+"</i><br>");
176                s.append("<b>+datum=...</b> - <i>"+tr("Datum name")+"</i><br>");
177                s.append("&nbsp;&nbsp;&nbsp;&nbsp;"+tr("Supported values:")+" ");
178                s.append(listKeys(Projections.datums)+"<br>");
179                s.append("<b>+towgs84=...</b> - <i>"+tr("3 or 7 term datum transform parameters")+"</i><br>");
180                s.append("<b>+nadgrids=...</b> - <i>"+tr("NTv2 grid file")+"</i><br>");
181                s.append("&nbsp;&nbsp;&nbsp;&nbsp;"+tr("Built-in:")+" ");
182                s.append(listKeys(Projections.nadgrids)+"<br>");
183                s.append("<b>+bounds=</b>minlon,minlat,maxlon,maxlat - <i>"+tr("Projection bounds (in degrees)")+"</i><br>");
184    
185                HtmlPanel info = new HtmlPanel(s.toString());
186                return info;
187            }
188    
189            private String listKeys(Map<String, ?> map) {
190                List<String> keys = new ArrayList<String>(map.keySet());
191                Collections.sort(keys);
192                return Utils.join(", ", keys);
193            }
194        }
195    
196        @Override
197        public void setPreferences(Collection<String> args) {
198            if (args != null && !args.isEmpty()) {
199                pref = args.iterator().next();
200            }
201        }
202    
203        @Override
204        public Projection getProjection() {
205            return new CustomProjection(pref);
206        }
207    
208        @Override
209        public JPanel getPreferencePanel(ActionListener listener) {
210            return new PreferencePanel(pref, listener);
211        }
212    
213        @Override
214        public Collection<String> getPreferences(JPanel panel) {
215            PreferencePanel prefPanel = (PreferencePanel) panel;
216            String pref = prefPanel.input.getText();
217            prefPanel.rememberHistory();
218            return Collections.singleton(pref);
219        }
220    
221        @Override
222        public String[] allCodes() {
223            return new String[0];
224        }
225    
226        @Override
227        public Collection<String> getPreferencesFromCode(String code) {
228            return null;
229        }
230    
231        @Override
232        public String getAlias() {
233            return "org.openstreetmap.josm.data.projection.CustomProjectionPrefGui";
234        }
235    
236        @Override
237        public boolean showProjectionCode() {
238            return false;
239        }
240    }