001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.layer.imagery;
003
004import java.util.Map;
005import java.util.concurrent.CopyOnWriteArrayList;
006
007import org.openstreetmap.gui.jmapviewer.interfaces.TileSource;
008import org.openstreetmap.josm.Main;
009import org.openstreetmap.josm.data.coor.EastNorth;
010import org.openstreetmap.josm.data.preferences.BooleanProperty;
011import org.openstreetmap.josm.gui.layer.AbstractTileSourceLayer;
012import org.openstreetmap.josm.tools.CheckParameterUtil;
013import org.openstreetmap.josm.tools.bugreport.BugReport;
014
015/**
016 * This are the preferences of how to display a {@link TileSource}.
017 * <p>
018 * They have been extracted from the {@link AbstractTileSourceLayer}. Each layer has one set of such settings.
019 * @author michael
020 * @since 10568
021 */
022public class TileSourceDisplaySettings {
023    /**
024     * A string returned by {@link DisplaySettingsChangeEvent#getChangedSetting()} if auto load was changed.
025     * @see TileSourceDisplaySettings#isAutoLoad()
026     */
027    public static final String AUTO_LOAD = "automatic-downloading";
028
029    /**
030     * A string returned by {@link DisplaySettingsChangeEvent#getChangedSetting()} if auto zoom was changed.
031     * @see TileSourceDisplaySettings#isAutoZoom()
032     */
033    public static final String AUTO_ZOOM = "automatically-change-resolution";
034
035    /**
036     * A string returned by {@link DisplaySettingsChangeEvent#getChangedSetting()} if the sow errors property was changed.
037     * @see TileSourceDisplaySettings#isShowErrors()
038     */
039    private static final String SHOW_ERRORS = "show-errors";
040
041    private static final String DISPLACEMENT = "displacement";
042
043    private static final String PREFERENCE_PREFIX = "imagery.generic";
044
045    /**
046     * The default auto load property
047     */
048    public static final BooleanProperty PROP_AUTO_LOAD = new BooleanProperty(PREFERENCE_PREFIX + ".default_autoload", true);
049
050    /**
051     * The default auto zoom property
052     */
053    public static final BooleanProperty PROP_AUTO_ZOOM = new BooleanProperty(PREFERENCE_PREFIX + ".default_autozoom", true);
054
055
056    /** if layers changes automatically, when user zooms in */
057    private boolean autoZoom;
058    /** if layer automatically loads new tiles */
059    private boolean autoLoad;
060    /** if layer should show errors on tiles */
061    private boolean showErrors;
062
063    /**
064     * The displacement
065     */
066    private EastNorth displacement = new EastNorth(0, 0);
067
068    private final CopyOnWriteArrayList<DisplaySettingsChangeListener> listeners = new CopyOnWriteArrayList<>();
069
070    /**
071     * Create a new {@link TileSourceDisplaySettings}
072     */
073    public TileSourceDisplaySettings() {
074        this(new String[] {PREFERENCE_PREFIX});
075    }
076
077    /**
078     * Create a new {@link TileSourceDisplaySettings}
079     * @param preferencePrefix The additional prefix to scan for preferences.
080     */
081    public TileSourceDisplaySettings(String preferencePrefix) {
082        this(PREFERENCE_PREFIX, preferencePrefix);
083    }
084
085    private TileSourceDisplaySettings(String... prefixes) {
086        autoZoom = getProperty(prefixes, "default_autozoom");
087        autoLoad = getProperty(prefixes, "default_autoload");
088        showErrors = getProperty(prefixes, "default_showerrors");
089    }
090
091    private static boolean getProperty(String[] prefixes, String name) {
092        // iterate through all values to force the preferences to receive the default value.
093        // we only support a default value of true.
094        boolean value = true;
095        for (String p : prefixes) {
096            String key = p + "." + name;
097            boolean currentValue = Main.pref.getBoolean(key, true);
098            if (!Main.pref.get(key).isEmpty()) {
099                value = currentValue;
100            }
101        }
102        return value;
103    }
104
105    /**
106     * Let the layer zoom automatically if the user zooms in
107     * @return auto zoom
108     */
109    public boolean isAutoZoom() {
110        return autoZoom;
111    }
112
113    /**
114     * Sets the auto zoom property
115     * @param autoZoom {@code true} to let the layer zoom automatically if the user zooms in
116     * @see #isAutoZoom()
117     * @see #AUTO_ZOOM
118     */
119    public void setAutoZoom(boolean autoZoom) {
120        this.autoZoom = autoZoom;
121        fireSettingsChange(AUTO_ZOOM);
122    }
123
124    /**
125     * Gets if the layer should automatically load new tiles.
126     * @return <code>true</code> if it should
127     */
128    public boolean isAutoLoad() {
129        return autoLoad;
130    }
131
132    /**
133     * Sets the auto load property
134     * @param autoLoad {@code true} if the layer should automatically load new tiles
135     * @see #isAutoLoad()
136     * @see #AUTO_LOAD
137     */
138    public void setAutoLoad(boolean autoLoad) {
139        this.autoLoad = autoLoad;
140        fireSettingsChange(AUTO_LOAD);
141    }
142
143    /**
144     * If the layer should display the errors it encountered while loading the tiles.
145     * @return <code>true</code> to show errors.
146     */
147    public boolean isShowErrors() {
148        return showErrors;
149    }
150
151    /**
152     * Sets the show errors property. Fires a change event.
153     * @param showErrors {@code true} if the layer should display the errors it encountered while loading the tiles
154     * @see #isShowErrors()
155     * @see #SHOW_ERRORS
156     */
157    public void setShowErrors(boolean showErrors) {
158        this.showErrors = showErrors;
159        fireSettingsChange(SHOW_ERRORS);
160    }
161
162    /**
163     * Gets the displacement in x (east) direction
164     * @return The displacement.
165     * @since 10571
166     */
167    public double getDx() {
168        return displacement.east();
169    }
170
171    /**
172     * Gets the displacement in y (north) direction
173     * @return The displacement.
174     * @since 10571
175     */
176    public double getDy() {
177        return displacement.north();
178    }
179
180    /**
181     * Gets the displacement of the image
182     * @return The displacement.
183     * @since 10571
184     */
185    public EastNorth getDisplacement() {
186        return displacement;
187    }
188
189    /**
190     * Set the displacement
191     * @param displacement The new displacement
192     * @since 10571
193     */
194    public void setDisplacement(EastNorth displacement) {
195        CheckParameterUtil.ensureValidCoordinates(displacement, "displacement");
196        this.displacement = displacement;
197        fireSettingsChange(DISPLACEMENT);
198    }
199
200    /**
201     * Adds the given value to the displacement.
202     * @param displacement The value to add.
203     * @since 10571
204     */
205    public void addDisplacement(EastNorth displacement) {
206        CheckParameterUtil.ensureValidCoordinates(displacement, "displacement");
207        setDisplacement(this.displacement.add(displacement));
208    }
209
210    /**
211     * Notifies all listeners that the paint settings have changed
212     * @param changedSetting The setting name
213     */
214    private void fireSettingsChange(String changedSetting) {
215        DisplaySettingsChangeEvent e = new DisplaySettingsChangeEvent(changedSetting);
216        for (DisplaySettingsChangeListener l : listeners) {
217            l.displaySettingsChanged(e);
218        }
219    }
220
221    /**
222     * Add a listener that listens to display settings changes.
223     * @param l The listener
224     */
225    public void addSettingsChangeListener(DisplaySettingsChangeListener l) {
226        listeners.add(l);
227    }
228
229    /**
230     * Remove a listener that listens to display settings changes.
231     * @param l The listener
232     */
233    public void removeSettingsChangeListener(DisplaySettingsChangeListener l) {
234        listeners.remove(l);
235    }
236
237    /**
238     * Stores the current settings object to the given hashmap.
239     * @param data The map to store the settings to.
240     * @see #loadFrom(Map)
241     */
242    public void storeTo(Map<String, String> data) {
243        data.put(AUTO_LOAD, Boolean.toString(autoLoad));
244        data.put(AUTO_ZOOM, Boolean.toString(autoZoom));
245        data.put(SHOW_ERRORS, Boolean.toString(showErrors));
246        data.put("dx", String.valueOf(getDx()));
247        data.put("dy", String.valueOf(getDy()));
248    }
249
250    /**
251     * Load the settings from the given data instance.
252     * @param data The data
253     * @see #storeTo(Map)
254     */
255    public void loadFrom(Map<String, String> data) {
256        try {
257            String doAutoLoad = data.get(AUTO_LOAD);
258            if (doAutoLoad != null) {
259                setAutoLoad(Boolean.parseBoolean(doAutoLoad));
260            }
261
262            String doAutoZoom = data.get(AUTO_ZOOM);
263            if (doAutoZoom != null) {
264                setAutoZoom(Boolean.parseBoolean(doAutoZoom));
265            }
266
267            String doShowErrors = data.get(SHOW_ERRORS);
268            if (doShowErrors != null) {
269                setShowErrors(Boolean.parseBoolean(doShowErrors));
270            }
271
272            String dx = data.get("dx");
273            String dy = data.get("dy");
274            if (dx != null && dy != null) {
275                setDisplacement(new EastNorth(Double.parseDouble(dx), Double.parseDouble(dy)));
276            }
277        } catch (RuntimeException e) {
278            throw BugReport.intercept(e).put("data", data);
279        }
280    }
281
282    @Override
283    public int hashCode() {
284        final int prime = 31;
285        int result = 1;
286        result = prime * result + (autoLoad ? 1231 : 1237);
287        result = prime * result + (autoZoom ? 1231 : 1237);
288        result = prime * result + (showErrors ? 1231 : 1237);
289        return result;
290    }
291
292    @Override
293    public boolean equals(Object obj) {
294        if (this == obj)
295            return true;
296        if (obj == null)
297            return false;
298        if (getClass() != obj.getClass())
299            return false;
300        TileSourceDisplaySettings other = (TileSourceDisplaySettings) obj;
301        if (autoLoad != other.autoLoad)
302            return false;
303        if (autoZoom != other.autoZoom)
304            return false;
305        if (showErrors != other.showErrors)
306            return false;
307        return true;
308    }
309
310    @Override
311    public String toString() {
312        return "TileSourceDisplaySettings [autoZoom=" + autoZoom + ", autoLoad=" + autoLoad + ", showErrors="
313                + showErrors + ']';
314    }
315
316    /**
317     * A listener that listens to changes to the {@link TileSourceDisplaySettings} object.
318     * @author Michael Zangl
319     * @since 10600 (functional interface)
320     */
321    @FunctionalInterface
322    public interface DisplaySettingsChangeListener {
323        /**
324         * Called whenever the display settings have changed.
325         * @param e The change event.
326         */
327        void displaySettingsChanged(DisplaySettingsChangeEvent e);
328    }
329
330    /**
331     * An event that is created whenever the display settings change.
332     * @author Michael Zangl
333     */
334    public static final class DisplaySettingsChangeEvent {
335        private final String changedSetting;
336
337        DisplaySettingsChangeEvent(String changedSetting) {
338            this.changedSetting = changedSetting;
339        }
340
341        /**
342         * Gets the setting that was changed
343         * @return The name of the changed setting.
344         */
345        public String getChangedSetting() {
346            return changedSetting;
347        }
348
349        @Override
350        public String toString() {
351            return "DisplaySettingsChangeEvent [changedSetting=" + changedSetting + ']';
352        }
353    }
354}