001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.imagery;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.event.ActionListener;
007import java.io.IOException;
008import java.net.MalformedURLException;
009import java.nio.file.InvalidPathException;
010import java.util.Collection;
011import java.util.List;
012import java.util.stream.Collectors;
013
014import javax.swing.DefaultComboBoxModel;
015import javax.swing.JButton;
016import javax.swing.JCheckBox;
017import javax.swing.JComboBox;
018import javax.swing.JLabel;
019import javax.swing.JOptionPane;
020import javax.swing.JScrollPane;
021
022import org.openstreetmap.josm.data.imagery.DefaultLayer;
023import org.openstreetmap.josm.data.imagery.ImageryInfo;
024import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
025import org.openstreetmap.josm.data.imagery.LayerDetails;
026import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
027import org.openstreetmap.josm.gui.util.GuiHelper;
028import org.openstreetmap.josm.gui.widgets.JosmTextArea;
029import org.openstreetmap.josm.io.imagery.WMSImagery;
030import org.openstreetmap.josm.tools.GBC;
031import org.openstreetmap.josm.tools.Logging;
032import org.openstreetmap.josm.tools.Utils;
033
034/**
035 * An imagery panel used to add WMS imagery sources.
036 * @since 2599
037 */
038public class AddWMSLayerPanel extends AddImageryPanel {
039
040    private transient WMSImagery wms;
041    private final JCheckBox endpoint = new JCheckBox(tr("Store WMS endpoint only, select layers at usage"));
042    private final JCheckBox setDefaultLayers = new JCheckBox(tr("Use selected layers as default"));
043    private final transient WMSLayerTree tree = new WMSLayerTree();
044    private final JComboBox<String> formats = new JComboBox<>();
045    private final JosmTextArea wmsUrl = new JosmTextArea(3, 40).transferFocusOnTab();
046    private final JButton showBounds = new JButton(tr("Show bounds"));
047
048    /**
049     * Constructs a new {@code AddWMSLayerPanel}.
050     */
051    public AddWMSLayerPanel() {
052
053        add(new JLabel(tr("{0} Make sure OSM has the permission to use this service", "1.")), GBC.eol());
054        add(new JLabel(tr("{0} Enter GetCapabilities URL", "2.")), GBC.eol());
055        add(rawUrl, GBC.eol().fill(GBC.HORIZONTAL));
056        rawUrl.setLineWrap(true);
057        JButton getLayers = new JButton(tr("{0} Get layers", "3."));
058        add(getLayers, GBC.eop().fill(GBC.HORIZONTAL));
059
060        add(new JLabel(tr("{0} Select layers", "4.")), GBC.eol());
061
062        add(endpoint, GBC.eol());
063        setDefaultLayers.setEnabled(false);
064        add(setDefaultLayers, GBC.eol());
065        add(new JScrollPane(tree.getLayerTree()), GBC.eol().fill());
066
067        showBounds.setEnabled(false);
068        add(showBounds, GBC.eop().fill(GBC.HORIZONTAL));
069
070        add(new JLabel(tr("{0} Select image format", "5.")), GBC.eol());
071        add(formats, GBC.eol().fill(GBC.HORIZONTAL));
072
073        addCommonSettings();
074
075        JLabel wmsInstruction = new JLabel(tr("{0} Edit generated {1} URL (optional)", "6.", "WMS"));
076        add(wmsInstruction, GBC.eol());
077        wmsInstruction.setLabelFor(wmsUrl);
078        add(wmsUrl, GBC.eop().fill(GBC.HORIZONTAL));
079        wmsUrl.setLineWrap(true);
080
081        add(new JLabel(tr("{0} Enter name for this layer", "7.")), GBC.eol());
082        add(name, GBC.eop().fill(GBC.HORIZONTAL));
083
084        getLayers.addActionListener(e -> {
085            try {
086                wms = new WMSImagery(Utils.strip(rawUrl.getText()), getCommonHeaders());
087                tree.updateTree(wms);
088                Collection<String> wmsFormats = wms.getFormats();
089                formats.setModel(new DefaultComboBoxModel<>(wmsFormats.toArray(new String[0])));
090                formats.setSelectedItem(wms.getPreferredFormat());
091            } catch (MalformedURLException | InvalidPathException ex1) {
092                Logging.log(Logging.LEVEL_ERROR, ex1);
093                JOptionPane.showMessageDialog(getParent(), tr("Invalid service URL."),
094                        tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
095            } catch (IOException ex2) {
096                Logging.log(Logging.LEVEL_ERROR, ex2);
097                JOptionPane.showMessageDialog(getParent(), tr("Could not retrieve WMS layer list."),
098                        tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
099            } catch (WMSImagery.WMSGetCapabilitiesException ex3) {
100                String incomingData = ex3.getIncomingData() != null ? ex3.getIncomingData().trim() : "";
101                String title = tr("WMS Error");
102                StringBuilder message = new StringBuilder(tr("Could not parse WMS layer list."));
103                Logging.log(Logging.LEVEL_ERROR, "Could not parse WMS layer list. Incoming data:\n"+incomingData, ex3);
104                if ((incomingData.startsWith("<html>") || incomingData.startsWith("<HTML>"))
105                  && (incomingData.endsWith("</html>") || incomingData.endsWith("</HTML>"))) {
106                    GuiHelper.notifyUserHtmlError(this, title, message.toString(), incomingData);
107                } else {
108                    if (ex3.getMessage() != null) {
109                        message.append('\n').append(ex3.getMessage());
110                    }
111                    JOptionPane.showMessageDialog(getParent(), message.toString(), title, JOptionPane.ERROR_MESSAGE);
112                }
113            }
114        });
115
116        ActionListener availabilityManagerAction = a -> {
117            setDefaultLayers.setEnabled(endpoint.isSelected());
118            boolean enabled = !endpoint.isSelected() || setDefaultLayers.isSelected();
119            tree.getLayerTree().setEnabled(enabled);
120            showBounds.setEnabled(enabled);
121            wmsInstruction.setEnabled(enabled);
122            formats.setEnabled(enabled);
123            wmsUrl.setEnabled(enabled);
124            if (endpoint.isSelected() && !setDefaultLayers.isSelected() && wms != null) {
125                name.setText(wms.buildRootUrlWithoutCapabilities());
126            }
127            onLayerSelectionChanged();
128        };
129
130        endpoint.addActionListener(availabilityManagerAction);
131        setDefaultLayers.addActionListener(availabilityManagerAction);
132
133        tree.getLayerTree().addPropertyChangeListener("selectedLayers", evt -> onLayerSelectionChanged());
134
135        formats.addActionListener(e -> onLayerSelectionChanged());
136
137        showBounds.addActionListener(e -> {
138            if (tree.getSelectedLayers().get(0).getBounds() != null) {
139                SlippyMapBBoxChooser mapPanel = new SlippyMapBBoxChooser();
140                mapPanel.setBoundingBox(tree.getSelectedLayers().get(0).getBounds());
141                JOptionPane.showMessageDialog(null, mapPanel, tr("Show Bounds"), JOptionPane.PLAIN_MESSAGE);
142            } else {
143                JOptionPane.showMessageDialog(null, tr("No bounding box was found for this layer."),
144                        tr("WMS Error"), JOptionPane.ERROR_MESSAGE);
145            }
146        });
147
148        registerValidableComponent(endpoint);
149        registerValidableComponent(rawUrl);
150        registerValidableComponent(wmsUrl);
151        registerValidableComponent(setDefaultLayers);
152    }
153
154    protected final void onLayerSelectionChanged() {
155        if (wms != null && wms.buildRootUrl() != null) {
156            wmsUrl.setText(wms.buildGetMapUrl(
157                    tree.getSelectedLayers().stream().map(LayerDetails::getName).collect(Collectors.toList()),
158                    (List<String>) null,
159                    (String) formats.getSelectedItem(),
160                    true // TODO: ask user about transparency
161                )
162            );
163            name.setText(wms.buildRootUrlWithoutCapabilities() + ": " +
164                    tree.getSelectedLayers().stream().map(LayerDetails::toString).collect(Collectors.joining(", ")));
165        }
166        showBounds.setEnabled(tree.getSelectedLayers().size() == 1);
167    }
168
169    @Override
170    public ImageryInfo getImageryInfo() {
171        ImageryInfo info = null;
172        if (endpoint.isSelected()) {
173            info = new ImageryInfo(getImageryName(), getImageryRawUrl());
174            info.setImageryType(ImageryInfo.ImageryType.WMS_ENDPOINT);
175            if (setDefaultLayers.isSelected()) {
176                info.setDefaultLayers(tree.getSelectedLayers().stream()
177                        .map(x -> new DefaultLayer(
178                                ImageryInfo.ImageryType.WMS_ENDPOINT,
179                                x.getName(),
180                                "", // TODO: allow selection of styles
181                                ""))
182                        .collect(Collectors.toList()));
183                info.setServerProjections(wms.getServerProjections(tree.getSelectedLayers()));
184            }
185        } else {
186            if (wms != null && wms.buildRootUrl() != null) {
187                // TODO: ask user about transparency
188                info = wms.toImageryInfo(
189                        getImageryName(), tree.getSelectedLayers(), (List<String>) null, (String) formats.getSelectedItem(), true);
190            } else {
191                info = new ImageryInfo(getImageryName(), getWmsUrl());
192            }
193            info.setImageryType(ImageryType.WMS);
194        }
195        info.setGeoreferenceValid(getCommonIsValidGeoreference());
196        info.setCustomHttpHeaders(getCommonHeaders());
197        return info;
198    }
199
200    protected final String getWmsUrl() {
201        return sanitize(wmsUrl.getText(), ImageryInfo.ImageryType.WMS);
202    }
203
204    @Override
205    protected boolean isImageryValid() {
206        if (getImageryName().isEmpty()) {
207            return false;
208        }
209        if (!endpoint.isSelected() && !getWmsUrl().isEmpty()) {
210            // if endpoint is not selected and imagery name and WMS URL is provided, allow adding imagery
211            // this is to cover the cases, when the user constructs URL on hers/his own
212            return true;
213        }
214        /*
215         * We need selection of the layers in following situations:
216         * * endpoint is not selected (and we don't care about setDefault layers as it's disabled anyway)
217         * * endpoint is selected and setDefaultLayers is selected
218         */
219        if ((!endpoint.isSelected() || setDefaultLayers.isSelected()) && (tree == null || tree.getSelectedLayers().isEmpty())) {
220            return false;
221        }
222        if (endpoint.isSelected()) {
223            return !getImageryRawUrl().isEmpty();
224        } else {
225            return !getWmsUrl().isEmpty();
226        }
227    }
228}