001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.gui.preferences.map; 003 004import static org.openstreetmap.josm.tools.I18n.marktr; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.GridBagLayout; 008import java.util.ArrayList; 009import java.util.Arrays; 010import java.util.Collection; 011import java.util.HashMap; 012import java.util.List; 013import java.util.Map; 014import java.util.Objects; 015import java.util.TreeSet; 016 017import javax.swing.BorderFactory; 018import javax.swing.JCheckBox; 019import javax.swing.JPanel; 020 021import org.openstreetmap.josm.Main; 022import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; 023import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 024import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 025import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 026import org.openstreetmap.josm.gui.preferences.SourceEditor; 027import org.openstreetmap.josm.gui.preferences.SourceEditor.ExtendedSourceEntry; 028import org.openstreetmap.josm.gui.preferences.SourceEntry; 029import org.openstreetmap.josm.gui.preferences.SourceProvider; 030import org.openstreetmap.josm.gui.preferences.SourceType; 031import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting; 032import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting; 033import org.openstreetmap.josm.tools.GBC; 034import org.openstreetmap.josm.tools.Predicate; 035import org.openstreetmap.josm.tools.Utils; 036 037/** 038 * Preference settings for map paint styles. 039 */ 040public class MapPaintPreference implements SubPreferenceSetting { 041 private SourceEditor sources; 042 private JCheckBox enableIconDefault; 043 044 private static final List<SourceProvider> styleSourceProviders = new ArrayList<>(); 045 046 /** 047 * Registers a new additional style source provider. 048 * @param provider The style source provider 049 * @return {@code true}, if the provider has been added, {@code false} otherwise 050 */ 051 public static boolean registerSourceProvider(SourceProvider provider) { 052 if (provider != null) 053 return styleSourceProviders.add(provider); 054 return false; 055 } 056 057 /** 058 * Factory used to create a new {@code MapPaintPreference}. 059 */ 060 public static class Factory implements PreferenceSettingFactory { 061 @Override 062 public PreferenceSetting createPreferenceSetting() { 063 return new MapPaintPreference(); 064 } 065 } 066 067 @Override 068 public void addGui(PreferenceTabbedPane gui) { 069 enableIconDefault = new JCheckBox(tr("Enable built-in icon defaults"), 070 Main.pref.getBoolean("mappaint.icon.enable-defaults", true)); 071 072 sources = new MapPaintSourceEditor(); 073 074 final JPanel panel = new JPanel(new GridBagLayout()); 075 panel.setBorder(BorderFactory.createEmptyBorder( 0, 0, 0, 0 )); 076 077 panel.add(sources, GBC.eol().fill(GBC.BOTH)); 078 panel.add(enableIconDefault, GBC.eol().insets(11,2,5,0)); 079 080 final MapPreference mapPref = gui.getMapPreference(); 081 mapPref.addSubTab(this, tr("Map Paint Styles"), panel); 082 sources.deferLoading(mapPref, panel); 083 } 084 085 static class MapPaintSourceEditor extends SourceEditor { 086 087 private static final String iconpref = "mappaint.icon.sources"; 088 089 public MapPaintSourceEditor() { 090 super(SourceType.MAP_PAINT_STYLE, Main.getJOSMWebsite()+"/styles", styleSourceProviders, true); 091 } 092 093 @Override 094 public Collection<? extends SourceEntry> getInitialSourcesList() { 095 return MapPaintPrefHelper.INSTANCE.get(); 096 } 097 098 @Override 099 public boolean finish() { 100 List<SourceEntry> activeStyles = activeSourcesModel.getSources(); 101 102 boolean changed = MapPaintPrefHelper.INSTANCE.put(activeStyles); 103 104 if (tblIconPaths != null) { 105 List<String> iconPaths = iconPathsModel.getIconPaths(); 106 107 if (!iconPaths.isEmpty()) { 108 if (Main.pref.putCollection(iconpref, iconPaths)) { 109 changed = true; 110 } 111 } else if (Main.pref.putCollection(iconpref, null)) { 112 changed = true; 113 } 114 } 115 return changed; 116 } 117 118 @Override 119 public Collection<ExtendedSourceEntry> getDefault() { 120 return MapPaintPrefHelper.INSTANCE.getDefault(); 121 } 122 123 @Override 124 public Collection<String> getInitialIconPathsList() { 125 return Main.pref.getCollection(iconpref, null); 126 } 127 128 @Override 129 public String getStr(I18nString ident) { 130 switch (ident) { 131 case AVAILABLE_SOURCES: 132 return tr("Available styles:"); 133 case ACTIVE_SOURCES: 134 return tr("Active styles:"); 135 case NEW_SOURCE_ENTRY_TOOLTIP: 136 return tr("Add a new style by entering filename or URL"); 137 case NEW_SOURCE_ENTRY: 138 return tr("New style entry:"); 139 case REMOVE_SOURCE_TOOLTIP: 140 return tr("Remove the selected styles from the list of active styles"); 141 case EDIT_SOURCE_TOOLTIP: 142 return tr("Edit the filename or URL for the selected active style"); 143 case ACTIVATE_TOOLTIP: 144 return tr("Add the selected available styles to the list of active styles"); 145 case RELOAD_ALL_AVAILABLE: 146 return marktr("Reloads the list of available styles from ''{0}''"); 147 case LOADING_SOURCES_FROM: 148 return marktr("Loading style sources from ''{0}''"); 149 case FAILED_TO_LOAD_SOURCES_FROM: 150 return marktr("<html>Failed to load the list of style sources from<br>" 151 + "''{0}''.<br>" 152 + "<br>" 153 + "Details (untranslated):<br>{1}</html>"); 154 case FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC: 155 return "/Preferences/Styles#FailedToLoadStyleSources"; 156 case ILLEGAL_FORMAT_OF_ENTRY: 157 return marktr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''"); 158 default: throw new AssertionError(); 159 } 160 } 161 162 } 163 164 @Override 165 public boolean ok() { 166 boolean reload = Main.pref.put("mappaint.icon.enable-defaults", enableIconDefault.isSelected()); 167 reload |= sources.finish(); 168 if (reload) { 169 MapPaintStyles.readFromPreferences(); 170 } 171 if (Main.isDisplayingMapView()) { 172 MapPaintStyles.getStyles().clearCached(); 173 } 174 return false; 175 } 176 177 /** 178 * Initialize the styles 179 */ 180 public static void initialize() { 181 MapPaintStyles.readFromPreferences(); 182 } 183 184 /** 185 * Helper class for map paint styles preferences. 186 */ 187 public static class MapPaintPrefHelper extends SourceEditor.SourcePrefHelper { 188 189 /** 190 * The unique instance. 191 */ 192 public static final MapPaintPrefHelper INSTANCE = new MapPaintPrefHelper(); 193 194 /** 195 * Constructs a new {@code MapPaintPrefHelper}. 196 */ 197 public MapPaintPrefHelper() { 198 super("mappaint.style.entries"); 199 } 200 201 @Override 202 public List<SourceEntry> get() { 203 List<SourceEntry> ls = super.get(); 204 if (insertNewDefaults(ls)) { 205 put(ls); 206 } 207 return ls; 208 } 209 210 /** 211 * If the selection of default styles changes in future releases, add 212 * the new entries to the user-configured list. Remember the known URLs, 213 * so an item that was deleted explicitly is not added again. 214 */ 215 private boolean insertNewDefaults(List<SourceEntry> list) { 216 boolean changed = false; 217 218 boolean addedMapcssStyle = false; // Migration code can be removed ~ Nov. 2014 219 220 Collection<String> knownDefaults = new TreeSet<>(Main.pref.getCollection("mappaint.style.known-defaults")); 221 222 Collection<ExtendedSourceEntry> defaults = getDefault(); 223 int insertionIdx = 0; 224 for (final SourceEntry def : defaults) { 225 int i = Utils.indexOf(list, 226 new Predicate<SourceEntry>() { 227 @Override 228 public boolean evaluate(SourceEntry se) { 229 return Objects.equals(def.url, se.url); 230 } 231 }); 232 if (i == -1 && !knownDefaults.contains(def.url)) { 233 def.active = false; 234 list.add(insertionIdx, def); 235 insertionIdx++; 236 changed = true; 237 /* Migration code can be removed ~ Nov. 2014 */ 238 if ("resource://styles/standard/elemstyles.mapcss".equals(def.url)) { 239 addedMapcssStyle = true; 240 } 241 } else { 242 if (i >= insertionIdx) { 243 insertionIdx = i + 1; 244 } 245 } 246 } 247 248 for (SourceEntry def : defaults) { 249 knownDefaults.add(def.url); 250 } 251 // XML style is not bundled anymore 252 knownDefaults.remove("resource://styles/standard/elemstyles.xml"); 253 Main.pref.putCollection("mappaint.style.known-defaults", knownDefaults); 254 255 /* Migration code can be removed ~ Nov. 2014 */ 256 if (addedMapcssStyle) { 257 // change title of the XML entry 258 // only do this once. If the user changes it afterward, do not touch 259 if (!Main.pref.getBoolean("mappaint.style.migration.changedXmlName", false)) { 260 SourceEntry josmXml = Utils.find(list, new Predicate<SourceEntry>() { 261 @Override 262 public boolean evaluate(SourceEntry se) { 263 return "resource://styles/standard/elemstyles.xml".equals(se.url); 264 } 265 }); 266 if (josmXml != null) { 267 josmXml.title = tr("JOSM default (XML; old version)"); 268 changed = true; 269 } 270 Main.pref.put("mappaint.style.migration.changedXmlName", true); 271 } 272 } 273 274 /* Migration code can be removed ~ Nov. 2014 */ 275 if (!Main.pref.getBoolean("mappaint.style.migration.switchedToMapCSS", false)) { 276 SourceEntry josmXml = Utils.find(list, new Predicate<SourceEntry>() { 277 @Override 278 public boolean evaluate(SourceEntry se) { 279 return "resource://styles/standard/elemstyles.xml".equals(se.url); 280 } 281 }); 282 SourceEntry josmMapCSS = Utils.find(list, new Predicate<SourceEntry>() { 283 @Override 284 public boolean evaluate(SourceEntry se) { 285 return "resource://styles/standard/elemstyles.mapcss".equals(se.url); 286 } 287 }); 288 if (josmXml != null && josmMapCSS != null && josmXml.active) { 289 josmMapCSS.active = true; 290 josmXml.active = false; 291 Main.info("Switched mappaint style from XML format to MapCSS (one time migration)."); 292 changed = true; 293 } 294 // in any case, do this check only once: 295 Main.pref.put("mappaint.style.migration.switchedToMapCSS", true); 296 } 297 298 // XML style is not bundled anymore 299 list.remove(Utils.find(list, new Predicate<SourceEntry>() { 300 @Override 301 public boolean evaluate(SourceEntry se) { 302 return "resource://styles/standard/elemstyles.xml".equals(se.url); 303 }})); 304 305 return changed; 306 } 307 308 @Override 309 public Collection<ExtendedSourceEntry> getDefault() { 310 ExtendedSourceEntry defJosmMapcss = new ExtendedSourceEntry("elemstyles.mapcss", "resource://styles/standard/elemstyles.mapcss"); 311 defJosmMapcss.active = true; 312 defJosmMapcss.name = "standard"; 313 defJosmMapcss.title = tr("JOSM default (MapCSS)"); 314 defJosmMapcss.description = tr("Internal style to be used as base for runtime switchable overlay styles"); 315 ExtendedSourceEntry defPL2 = new ExtendedSourceEntry("potlatch2.mapcss", "resource://styles/standard/potlatch2.mapcss"); 316 defPL2.active = false; 317 defPL2.name = "standard"; 318 defPL2.title = tr("Potlatch 2"); 319 defPL2.description = tr("the main Potlatch 2 style"); 320 321 return Arrays.asList(new ExtendedSourceEntry[] { defJosmMapcss, defPL2 }); 322 } 323 324 @Override 325 public Map<String, String> serialize(SourceEntry entry) { 326 Map<String, String> res = new HashMap<>(); 327 res.put("url", entry.url); 328 res.put("title", entry.title == null ? "" : entry.title); 329 res.put("active", Boolean.toString(entry.active)); 330 if (entry.name != null) { 331 res.put("ptoken", entry.name); 332 } 333 return res; 334 } 335 336 @Override 337 public SourceEntry deserialize(Map<String, String> s) { 338 return new SourceEntry(s.get("url"), s.get("ptoken"), s.get("title"), Boolean.parseBoolean(s.get("active"))); 339 } 340 } 341 342 @Override 343 public boolean isExpert() { 344 return false; 345 } 346 347 @Override 348 public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) { 349 return gui.getMapPreference(); 350 } 351}