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.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 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 return doFinish(MapPaintPrefHelper.INSTANCE, ICONPREF); 101 } 102 103 @Override 104 public Collection<ExtendedSourceEntry> getDefault() { 105 return MapPaintPrefHelper.INSTANCE.getDefault(); 106 } 107 108 @Override 109 public Collection<String> getInitialIconPathsList() { 110 return Main.pref.getCollection(ICONPREF, null); 111 } 112 113 @Override 114 public String getStr(I18nString ident) { 115 switch (ident) { 116 case AVAILABLE_SOURCES: 117 return tr("Available styles:"); 118 case ACTIVE_SOURCES: 119 return tr("Active styles:"); 120 case NEW_SOURCE_ENTRY_TOOLTIP: 121 return tr("Add a new style by entering filename or URL"); 122 case NEW_SOURCE_ENTRY: 123 return tr("New style entry:"); 124 case REMOVE_SOURCE_TOOLTIP: 125 return tr("Remove the selected styles from the list of active styles"); 126 case EDIT_SOURCE_TOOLTIP: 127 return tr("Edit the filename or URL for the selected active style"); 128 case ACTIVATE_TOOLTIP: 129 return tr("Add the selected available styles to the list of active styles"); 130 case RELOAD_ALL_AVAILABLE: 131 return marktr("Reloads the list of available styles from ''{0}''"); 132 case LOADING_SOURCES_FROM: 133 return marktr("Loading style sources from ''{0}''"); 134 case FAILED_TO_LOAD_SOURCES_FROM: 135 return marktr("<html>Failed to load the list of style sources from<br>" 136 + "''{0}''.<br>" 137 + "<br>" 138 + "Details (untranslated):<br>{1}</html>"); 139 case FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC: 140 return "/Preferences/Styles#FailedToLoadStyleSources"; 141 case ILLEGAL_FORMAT_OF_ENTRY: 142 return marktr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''"); 143 default: throw new AssertionError(); 144 } 145 } 146 147 @Override 148 protected String getTitleForSourceEntry(SourceEntry entry) { 149 final String title = getTitleFromSourceEntry(entry); 150 return title != null ? title : super.getTitleForSourceEntry(entry); 151 } 152 } 153 154 /** 155 * Returns title from a source entry. 156 * @param entry source entry 157 * @return title 158 * @see MapCSSStyleSource#title 159 */ 160 public static String getTitleFromSourceEntry(SourceEntry entry) { 161 try { 162 final MapCSSStyleSource css = new MapCSSStyleSource(entry); 163 css.loadStyleSource(); 164 if (css.title != null && !css.title.isEmpty()) { 165 return css.title; 166 } 167 } catch (RuntimeException ignore) { 168 Main.debug(ignore); 169 } 170 return null; 171 } 172 173 @Override 174 public boolean ok() { 175 boolean reload = Main.pref.put("mappaint.icon.enable-defaults", enableIconDefault.isSelected()); 176 reload |= sources.finish(); 177 if (reload) { 178 MapPaintStyles.readFromPreferences(); 179 } 180 if (Main.isDisplayingMapView()) { 181 MapPaintStyles.getStyles().clearCached(); 182 } 183 return false; 184 } 185 186 /** 187 * Initialize the styles 188 */ 189 public static void initialize() { 190 MapPaintStyles.readFromPreferences(); 191 } 192 193 /** 194 * Helper class for map paint styles preferences. 195 */ 196 public static class MapPaintPrefHelper extends SourceEditor.SourcePrefHelper { 197 198 /** 199 * The unique instance. 200 */ 201 public static final MapPaintPrefHelper INSTANCE = new MapPaintPrefHelper(); 202 203 /** 204 * Constructs a new {@code MapPaintPrefHelper}. 205 */ 206 public MapPaintPrefHelper() { 207 super("mappaint.style.entries"); 208 } 209 210 @Override 211 public List<SourceEntry> get() { 212 List<SourceEntry> ls = super.get(); 213 if (insertNewDefaults(ls)) { 214 put(ls); 215 } 216 return ls; 217 } 218 219 /** 220 * If the selection of default styles changes in future releases, add 221 * the new entries to the user-configured list. Remember the known URLs, 222 * so an item that was deleted explicitly is not added again. 223 * @param list new defaults 224 * @return {@code true} if a change occurred 225 */ 226 private boolean insertNewDefaults(List<SourceEntry> list) { 227 boolean changed = false; 228 229 Collection<String> knownDefaults = new TreeSet<>(Main.pref.getCollection("mappaint.style.known-defaults")); 230 231 Collection<ExtendedSourceEntry> defaults = getDefault(); 232 int insertionIdx = 0; 233 for (final SourceEntry def : defaults) { 234 int i = Utils.indexOf(list, se -> Objects.equals(def.url, se.url)); 235 if (i == -1 && !knownDefaults.contains(def.url)) { 236 def.active = false; 237 list.add(insertionIdx, def); 238 insertionIdx++; 239 changed = true; 240 } else { 241 if (i >= insertionIdx) { 242 insertionIdx = i + 1; 243 } 244 } 245 knownDefaults.add(def.url); 246 } 247 Main.pref.putCollection("mappaint.style.known-defaults", knownDefaults); 248 249 // XML style is not bundled anymore 250 list.remove(Utils.find(list, se -> "resource://styles/standard/elemstyles.xml".equals(se.url))); 251 252 return changed; 253 } 254 255 @Override 256 public Collection<ExtendedSourceEntry> getDefault() { 257 ExtendedSourceEntry defJosmMapcss = new ExtendedSourceEntry("elemstyles.mapcss", "resource://styles/standard/elemstyles.mapcss"); 258 defJosmMapcss.active = true; 259 defJosmMapcss.name = "standard"; 260 defJosmMapcss.title = tr("JOSM default (MapCSS)"); 261 defJosmMapcss.description = tr("Internal style to be used as base for runtime switchable overlay styles"); 262 ExtendedSourceEntry defPL2 = new ExtendedSourceEntry("potlatch2.mapcss", "resource://styles/standard/potlatch2.mapcss"); 263 defPL2.active = false; 264 defPL2.name = "standard"; 265 defPL2.title = tr("Potlatch 2"); 266 defPL2.description = tr("the main Potlatch 2 style"); 267 268 return Arrays.asList(new ExtendedSourceEntry[] {defJosmMapcss, defPL2}); 269 } 270 271 @Override 272 public Map<String, String> serialize(SourceEntry entry) { 273 Map<String, String> res = new HashMap<>(); 274 res.put("url", entry.url); 275 res.put("title", entry.title == null ? "" : entry.title); 276 res.put("active", Boolean.toString(entry.active)); 277 if (entry.name != null) { 278 res.put("ptoken", entry.name); 279 } 280 return res; 281 } 282 283 @Override 284 public SourceEntry deserialize(Map<String, String> s) { 285 return new SourceEntry(s.get("url"), s.get("ptoken"), s.get("title"), Boolean.parseBoolean(s.get("active"))); 286 } 287 } 288 289 @Override 290 public boolean isExpert() { 291 return false; 292 } 293 294 @Override 295 public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) { 296 return gui.getMapPreference(); 297 } 298}