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