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.png")); 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.png")); 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(" "+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(" "+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(" "+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(" "+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}