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