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}