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