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