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.GridBagLayout;
007import java.awt.LayoutManager;
008import java.awt.event.ItemEvent;
009import java.util.ArrayList;
010import java.util.Arrays;
011import java.util.Collection;
012import java.util.List;
013import java.util.Map;
014import java.util.concurrent.TimeUnit;
015
016import javax.swing.AbstractButton;
017import javax.swing.JCheckBox;
018import javax.swing.JComboBox;
019import javax.swing.JLabel;
020import javax.swing.JPanel;
021import javax.swing.JSpinner;
022import javax.swing.SpinnerNumberModel;
023import javax.swing.text.JTextComponent;
024
025import org.openstreetmap.josm.actions.ExpertToggleAction;
026import org.openstreetmap.josm.data.imagery.ImageryInfo;
027import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryType;
028import org.openstreetmap.josm.data.imagery.TMSCachedTileLoaderJob;
029import org.openstreetmap.josm.gui.util.DocumentAdapter;
030import org.openstreetmap.josm.gui.widgets.JosmTextArea;
031import org.openstreetmap.josm.gui.widgets.JosmTextField;
032import org.openstreetmap.josm.tools.GBC;
033import org.openstreetmap.josm.tools.Logging;
034
035/**
036 * An abstract imagery panel used to add WMS/TMS imagery sources. See implementations.
037 * @see AddTMSLayerPanel
038 * @see AddWMSLayerPanel
039 * @see AddWMTSLayerPanel
040 * @since 5617
041 */
042public abstract class AddImageryPanel extends JPanel {
043
044    protected final JosmTextArea rawUrl = new JosmTextArea(3, 40).transferFocusOnTab();
045    protected final JosmTextField name = new JosmTextField();
046
047    protected final transient Collection<ContentValidationListener> listeners = new ArrayList<>();
048
049    private final JCheckBox validGeoreference = new JCheckBox(tr("Is layer properly georeferenced?"));
050    private HeadersTable headersTable;
051    private JSpinner minimumCacheExpiry;
052    private JComboBox<String> minimumCacheExpiryUnit;
053    private TimeUnit currentUnit;
054
055    /**
056     * A listener notified when the validation status of this panel change.
057     * @since 10600 (functional interface)
058     */
059    @FunctionalInterface
060    public interface ContentValidationListener {
061        /**
062         * Called when the validation status of this panel changed
063         * @param isValid true if the conditions required to close this panel are met
064         */
065        void contentChanged(boolean isValid);
066    }
067
068    protected AddImageryPanel() {
069        this(new GridBagLayout());
070        headersTable = new HeadersTable();
071        minimumCacheExpiry = new JSpinner(new SpinnerNumberModel(
072                (Number) TimeUnit.MILLISECONDS.toSeconds(TMSCachedTileLoaderJob.MINIMUM_EXPIRES.get()),
073                0L,
074                Long.valueOf(Integer.MAX_VALUE),
075                1
076                ));
077        List<String> units = Arrays.asList(tr("seconds"), tr("minutes"), tr("hours"), tr("days"));
078        minimumCacheExpiryUnit = new JComboBox<>(units.toArray(new String[]{}));
079        currentUnit = TimeUnit.SECONDS;
080        minimumCacheExpiryUnit.addItemListener(e -> {
081            if (e.getStateChange() == ItemEvent.SELECTED) {
082                long newValue = 0;
083                switch (units.indexOf(e.getItem())) {
084                case 0:
085                    newValue = currentUnit.toSeconds((long) minimumCacheExpiry.getValue());
086                    currentUnit = TimeUnit.SECONDS;
087                    break;
088                case 1:
089                    newValue = currentUnit.toMinutes((long) minimumCacheExpiry.getValue());
090                    currentUnit = TimeUnit.MINUTES;
091                    break;
092                case 2:
093                    newValue = currentUnit.toHours((long) minimumCacheExpiry.getValue());
094                    currentUnit = TimeUnit.HOURS;
095                    break;
096                case 3:
097                    newValue = currentUnit.toDays((long) minimumCacheExpiry.getValue());
098                    currentUnit = TimeUnit.DAYS;
099                    break;
100                default:
101                    Logging.warn("Unknown unit: " + units.indexOf(e.getItem()));
102                }
103                minimumCacheExpiry.setValue(newValue);
104            }
105        });
106
107
108    }
109
110    protected void addCommonSettings() {
111        if (ExpertToggleAction.isExpert()) {
112            add(new JLabel(tr("Minimum cache expiry: ")));
113            add(minimumCacheExpiry);
114            add(minimumCacheExpiryUnit, GBC.eol());
115            add(new JLabel(tr("Set custom HTTP headers (if needed):")), GBC.eop());
116            add(headersTable, GBC.eol().fill());
117            add(validGeoreference, GBC.eop().fill(GBC.HORIZONTAL));
118        }
119    }
120
121    protected Map<String, String> getCommonHeaders() {
122        return headersTable.getHeaders();
123    }
124
125    protected boolean getCommonIsValidGeoreference() {
126        return validGeoreference.isSelected();
127    }
128
129    protected AddImageryPanel(LayoutManager layout) {
130        super(layout);
131        registerValidableComponent(name);
132    }
133
134    protected final void registerValidableComponent(AbstractButton component) {
135        component.addChangeListener(e -> notifyListeners());
136    }
137
138    protected final void registerValidableComponent(JTextComponent component) {
139        component.getDocument().addDocumentListener(DocumentAdapter.create(ignore -> notifyListeners()));
140    }
141
142    protected abstract ImageryInfo getImageryInfo();
143
144    protected static String sanitize(String s) {
145        return s.replaceAll("[\r\n]+", "").trim();
146    }
147
148    protected static String sanitize(String s, ImageryType type) {
149        String ret = s;
150        String imageryType = type.getTypeString() + ':';
151        if (ret.startsWith(imageryType)) {
152            // remove ImageryType from URL
153            ret = ret.substring(imageryType.length());
154        }
155        return sanitize(ret);
156    }
157
158    protected final String getImageryName() {
159        return sanitize(name.getText());
160    }
161
162    protected final String getImageryRawUrl() {
163        return sanitize(rawUrl.getText());
164    }
165
166    protected abstract boolean isImageryValid();
167
168    /**
169     * Registers a new ContentValidationListener
170     * @param l The new ContentValidationListener that will be notified of validation status changes
171     */
172    public final void addContentValidationListener(ContentValidationListener l) {
173        if (l != null) {
174            listeners.add(l);
175        }
176    }
177
178    private void notifyListeners() {
179        for (ContentValidationListener l : listeners) {
180            l.contentChanged(isImageryValid());
181        }
182    }
183}