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