// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.gui.preferences.map; import static org.openstreetmap.josm.tools.I18n.marktr; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.GridBagLayout; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeSet; import javax.swing.BorderFactory; import javax.swing.JCheckBox; import javax.swing.JPanel; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; import org.openstreetmap.josm.gui.mappaint.mapcss.MapCSSStyleSource; import org.openstreetmap.josm.gui.preferences.PreferenceSetting; import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; import org.openstreetmap.josm.gui.preferences.SourceEditor; import org.openstreetmap.josm.gui.preferences.SourceEditor.ExtendedSourceEntry; import org.openstreetmap.josm.gui.preferences.SourceEntry; import org.openstreetmap.josm.gui.preferences.SourceProvider; import org.openstreetmap.josm.gui.preferences.SourceType; import org.openstreetmap.josm.gui.preferences.SubPreferenceSetting; import org.openstreetmap.josm.gui.preferences.TabPreferenceSetting; import org.openstreetmap.josm.tools.GBC; import org.openstreetmap.josm.tools.Utils; /** * Preference settings for map paint styles. */ public class MapPaintPreference implements SubPreferenceSetting { private SourceEditor sources; private JCheckBox enableIconDefault; private static final List<SourceProvider> styleSourceProviders = new ArrayList<>(); /** * Registers a new additional style source provider. * @param provider The style source provider * @return {@code true}, if the provider has been added, {@code false} otherwise */ public static boolean registerSourceProvider(SourceProvider provider) { if (provider != null) return styleSourceProviders.add(provider); return false; } /** * Factory used to create a new {@code MapPaintPreference}. */ public static class Factory implements PreferenceSettingFactory { @Override public PreferenceSetting createPreferenceSetting() { return new MapPaintPreference(); } } @Override public void addGui(PreferenceTabbedPane gui) { enableIconDefault = new JCheckBox(tr("Enable built-in icon defaults"), Main.pref.getBoolean("mappaint.icon.enable-defaults", true)); sources = new MapPaintSourceEditor(); final JPanel panel = new JPanel(new GridBagLayout()); panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); panel.add(sources, GBC.eol().fill(GBC.BOTH)); panel.add(enableIconDefault, GBC.eol().insets(11, 2, 5, 0)); final MapPreference mapPref = gui.getMapPreference(); mapPref.addSubTab(this, tr("Map Paint Styles"), panel); sources.deferLoading(mapPref, panel); } static class MapPaintSourceEditor extends SourceEditor { private static final String ICONPREF = "mappaint.icon.sources"; MapPaintSourceEditor() { super(SourceType.MAP_PAINT_STYLE, Main.getJOSMWebsite()+"/styles", styleSourceProviders, true); } @Override public Collection<? extends SourceEntry> getInitialSourcesList() { return MapPaintPrefHelper.INSTANCE.get(); } @Override public boolean finish() { return doFinish(MapPaintPrefHelper.INSTANCE, ICONPREF); } @Override public Collection<ExtendedSourceEntry> getDefault() { return MapPaintPrefHelper.INSTANCE.getDefault(); } @Override public Collection<String> getInitialIconPathsList() { return Main.pref.getCollection(ICONPREF, null); } @Override public String getStr(I18nString ident) { switch (ident) { case AVAILABLE_SOURCES: return tr("Available styles:"); case ACTIVE_SOURCES: return tr("Active styles:"); case NEW_SOURCE_ENTRY_TOOLTIP: return tr("Add a new style by entering filename or URL"); case NEW_SOURCE_ENTRY: return tr("New style entry:"); case REMOVE_SOURCE_TOOLTIP: return tr("Remove the selected styles from the list of active styles"); case EDIT_SOURCE_TOOLTIP: return tr("Edit the filename or URL for the selected active style"); case ACTIVATE_TOOLTIP: return tr("Add the selected available styles to the list of active styles"); case RELOAD_ALL_AVAILABLE: return marktr("Reloads the list of available styles from ''{0}''"); case LOADING_SOURCES_FROM: return marktr("Loading style sources from ''{0}''"); case FAILED_TO_LOAD_SOURCES_FROM: return marktr("<html>Failed to load the list of style sources from<br>" + "''{0}''.<br>" + "<br>" + "Details (untranslated):<br>{1}</html>"); case FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC: return "/Preferences/Styles#FailedToLoadStyleSources"; case ILLEGAL_FORMAT_OF_ENTRY: return marktr("Warning: illegal format of entry in style list ''{0}''. Got ''{1}''"); default: throw new AssertionError(); } } @Override protected String getTitleForSourceEntry(SourceEntry entry) { final String title = getTitleFromSourceEntry(entry); return title != null ? title : super.getTitleForSourceEntry(entry); } } /** * Returns title from a source entry. * @param entry source entry * @return title * @see MapCSSStyleSource#title */ public static String getTitleFromSourceEntry(SourceEntry entry) { try { final MapCSSStyleSource css = new MapCSSStyleSource(entry); css.loadStyleSource(); if (css.title != null && !css.title.isEmpty()) { return css.title; } } catch (RuntimeException ignore) { // NOPMD Main.debug(ignore); } return null; } @Override public boolean ok() { boolean reload = Main.pref.put("mappaint.icon.enable-defaults", enableIconDefault.isSelected()); reload |= sources.finish(); if (reload) { MapPaintStyles.readFromPreferences(); } if (Main.isDisplayingMapView()) { MapPaintStyles.getStyles().clearCached(); } return false; } /** * Initialize the styles */ public static void initialize() { MapPaintStyles.readFromPreferences(); } /** * Helper class for map paint styles preferences. */ public static class MapPaintPrefHelper extends SourceEditor.SourcePrefHelper { /** * The unique instance. */ public static final MapPaintPrefHelper INSTANCE = new MapPaintPrefHelper(); /** * Constructs a new {@code MapPaintPrefHelper}. */ public MapPaintPrefHelper() { super("mappaint.style.entries"); } @Override public List<SourceEntry> get() { List<SourceEntry> ls = super.get(); if (insertNewDefaults(ls)) { put(ls); } return ls; } /** * If the selection of default styles changes in future releases, add * the new entries to the user-configured list. Remember the known URLs, * so an item that was deleted explicitly is not added again. * @param list new defaults * @return {@code true} if a change occurred */ private boolean insertNewDefaults(List<SourceEntry> list) { boolean changed = false; Collection<String> knownDefaults = new TreeSet<>(Main.pref.getCollection("mappaint.style.known-defaults")); Collection<ExtendedSourceEntry> defaults = getDefault(); int insertionIdx = 0; for (final SourceEntry def : defaults) { int i = Utils.indexOf(list, se -> Objects.equals(def.url, se.url)); if (i == -1 && !knownDefaults.contains(def.url)) { def.active = false; list.add(insertionIdx, def); insertionIdx++; changed = true; } else { if (i >= insertionIdx) { insertionIdx = i + 1; } } knownDefaults.add(def.url); } Main.pref.putCollection("mappaint.style.known-defaults", knownDefaults); // XML style is not bundled anymore list.remove(Utils.find(list, se -> "resource://styles/standard/elemstyles.xml".equals(se.url))); return changed; } @Override public Collection<ExtendedSourceEntry> getDefault() { ExtendedSourceEntry defJosmMapcss = new ExtendedSourceEntry("elemstyles.mapcss", "resource://styles/standard/elemstyles.mapcss"); defJosmMapcss.active = true; defJosmMapcss.name = "standard"; defJosmMapcss.title = tr("JOSM default (MapCSS)"); defJosmMapcss.description = tr("Internal style to be used as base for runtime switchable overlay styles"); ExtendedSourceEntry defPL2 = new ExtendedSourceEntry("potlatch2.mapcss", "resource://styles/standard/potlatch2.mapcss"); defPL2.active = false; defPL2.name = "standard"; defPL2.title = tr("Potlatch 2"); defPL2.description = tr("the main Potlatch 2 style"); return Arrays.asList(new ExtendedSourceEntry[] {defJosmMapcss, defPL2}); } @Override public Map<String, String> serialize(SourceEntry entry) { Map<String, String> res = new HashMap<>(); res.put("url", entry.url == null ? "" : entry.url); res.put("title", entry.title == null ? "" : entry.title); res.put("active", Boolean.toString(entry.active)); if (entry.name != null) { res.put("ptoken", entry.name); } return res; } @Override public SourceEntry deserialize(Map<String, String> s) { return new SourceEntry(s.get("url"), s.get("ptoken"), s.get("title"), Boolean.parseBoolean(s.get("active"))); } } @Override public boolean isExpert() { return false; } @Override public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) { return gui.getMapPreference(); } }