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