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.io.IOException;
009import java.util.ArrayList;
010import java.util.Collection;
011import java.util.List;
012
013import javax.swing.BorderFactory;
014import javax.swing.JCheckBox;
015import javax.swing.JLabel;
016import javax.swing.JOptionPane;
017import javax.swing.JPanel;
018
019import org.openstreetmap.josm.data.preferences.sources.ExtendedSourceEntry;
020import org.openstreetmap.josm.data.preferences.sources.PresetPrefHelper;
021import org.openstreetmap.josm.data.preferences.sources.SourceEntry;
022import org.openstreetmap.josm.data.preferences.sources.SourceProvider;
023import org.openstreetmap.josm.data.preferences.sources.SourceType;
024import org.openstreetmap.josm.gui.ExtendedDialog;
025import org.openstreetmap.josm.gui.MainApplication;
026import org.openstreetmap.josm.gui.help.HelpUtil;
027import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
028import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
029import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
030import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
031import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.PreferencePanel;
032import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane.ValidationListener;
033import org.openstreetmap.josm.gui.preferences.SourceEditor;
034import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
035import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetReader;
036import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
037import org.openstreetmap.josm.spi.preferences.Config;
038import org.openstreetmap.josm.tools.GBC;
039import org.openstreetmap.josm.tools.ImageProvider;
040import org.openstreetmap.josm.tools.Logging;
041import org.openstreetmap.josm.tools.Utils;
042import org.xml.sax.SAXException;
043import org.xml.sax.SAXParseException;
044
045/**
046 * Preference settings for tagging presets.
047 */
048public final class TaggingPresetPreference extends DefaultTabPreferenceSetting {
049
050    private final class TaggingPresetValidationListener implements ValidationListener {
051        @Override
052        public boolean validatePreferences() {
053            if (sources.hasActiveSourcesChanged()) {
054                List<Integer> sourcesToRemove = new ArrayList<>();
055                int i = -1;
056                SOURCES:
057                    for (SourceEntry source: sources.getActiveSources()) {
058                        i++;
059                        boolean canLoad = false;
060                        try {
061                            TaggingPresetReader.readAll(source.url, false);
062                            canLoad = true;
063                        } catch (IOException e) {
064                            Logging.log(Logging.LEVEL_WARN, tr("Could not read tagging preset source: {0}", source), e);
065                            ExtendedDialog ed = new ExtendedDialog(MainApplication.getMainFrame(), tr("Error"),
066                                    tr("Yes"), tr("No"), tr("Cancel"));
067                            ed.setContent(tr("Could not read tagging preset source: {0}\nDo you want to keep it?", source));
068                            switch (ed.showDialog().getValue()) {
069                            case 1:
070                                continue SOURCES;
071                            case 2:
072                                sourcesToRemove.add(i);
073                                continue SOURCES;
074                            default:
075                                return false;
076                            }
077                        } catch (SAXException e) {
078                            // We will handle this in step with validation
079                            Logging.trace(e);
080                        }
081
082                        String errorMessage = null;
083
084                        try {
085                            TaggingPresetReader.readAll(source.url, true);
086                        } catch (IOException e) {
087                            // Should not happen, but at least show message
088                            String msg = tr("Could not read tagging preset source: {0}", source);
089                            Logging.log(Logging.LEVEL_ERROR, msg, e);
090                            JOptionPane.showMessageDialog(MainApplication.getMainFrame(), msg);
091                            return false;
092                        } catch (SAXParseException e) {
093                            if (canLoad) {
094                                errorMessage = tr("<html>Tagging preset source {0} can be loaded but it contains errors. " +
095                                        "Do you really want to use it?<br><br><table width=600>Error is: [{1}:{2}] {3}</table></html>",
096                                        source, e.getLineNumber(), e.getColumnNumber(), Utils.escapeReservedCharactersHTML(e.getMessage()));
097                            } else {
098                                errorMessage = tr("<html>Unable to parse tagging preset source: {0}. " +
099                                        "Do you really want to use it?<br><br><table width=400>Error is: [{1}:{2}] {3}</table></html>",
100                                        source, e.getLineNumber(), e.getColumnNumber(), Utils.escapeReservedCharactersHTML(e.getMessage()));
101                            }
102                            Logging.log(Logging.LEVEL_WARN, errorMessage, e);
103                        } catch (SAXException e) {
104                            if (canLoad) {
105                                errorMessage = tr("<html>Tagging preset source {0} can be loaded but it contains errors. " +
106                                        "Do you really want to use it?<br><br><table width=600>Error is: {1}</table></html>",
107                                        source, Utils.escapeReservedCharactersHTML(e.getMessage()));
108                            } else {
109                                errorMessage = tr("<html>Unable to parse tagging preset source: {0}. " +
110                                        "Do you really want to use it?<br><br><table width=600>Error is: {1}</table></html>",
111                                        source, Utils.escapeReservedCharactersHTML(e.getMessage()));
112                            }
113                            Logging.log(Logging.LEVEL_ERROR, errorMessage, e);
114                        }
115
116                        if (errorMessage != null) {
117                            Logging.error(errorMessage);
118                            int result = JOptionPane.showConfirmDialog(MainApplication.getMainFrame(), new JLabel(errorMessage), tr("Error"),
119                                    JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE);
120
121                            switch (result) {
122                            case JOptionPane.YES_OPTION:
123                                continue SOURCES;
124                            case JOptionPane.NO_OPTION:
125                                sourcesToRemove.add(i);
126                                continue SOURCES;
127                            default:
128                                return false;
129                            }
130                        }
131                    }
132                sources.removeSources(sourcesToRemove);
133                return true;
134            } else {
135                return true;
136            }
137        }
138    }
139
140    /**
141     * Factory used to create a new {@code TaggingPresetPreference}.
142     */
143    public static class Factory implements PreferenceSettingFactory {
144        @Override
145        public PreferenceSetting createPreferenceSetting() {
146            return new TaggingPresetPreference();
147        }
148    }
149
150    private TaggingPresetPreference() {
151        super("dialogs/propertiesdialog", tr("Tagging Presets"), tr("Tagging Presets"));
152    }
153
154    private static final List<SourceProvider> presetSourceProviders = new ArrayList<>();
155
156    private SourceEditor sources;
157    private JCheckBox useValidator;
158    private JCheckBox sortMenu;
159
160    /**
161     * Registers a new additional preset source provider.
162     * @param provider The preset source provider
163     * @return {@code true}, if the provider has been added, {@code false} otherwise
164     */
165    public static boolean registerSourceProvider(SourceProvider provider) {
166        if (provider != null)
167            return presetSourceProviders.add(provider);
168        return false;
169    }
170
171    private final ValidationListener validationListener = new TaggingPresetValidationListener();
172
173    @Override
174    public void addGui(PreferenceTabbedPane gui) {
175        useValidator = new JCheckBox(tr("Run data validator on user input"), TaggingPreset.USE_VALIDATOR.get());
176        sortMenu = new JCheckBox(tr("Sort presets menu alphabetically"), TaggingPresets.SORT_MENU.get());
177
178        final JPanel panel = new JPanel(new GridBagLayout());
179        panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
180
181        panel.add(useValidator, GBC.std().insets(5, 5, 0, 0));
182        panel.add(new JLabel(ImageProvider.get("dialogs/validator")), GBC.eol().insets(5, 5, 0, 0));
183        panel.add(sortMenu, GBC.eol().insets(5, 0, 5, 0));
184
185        sources = new TaggingPresetSourceEditor();
186        panel.add(sources, GBC.eol().fill(GBC.BOTH));
187        PreferencePanel preferencePanel = gui.createPreferenceTab(this);
188        preferencePanel.add(panel, GBC.eol().fill(GBC.BOTH));
189        sources.deferLoading(gui, preferencePanel);
190        gui.addValidationListener(validationListener);
191    }
192
193    public static class TaggingPresetSourceEditor extends SourceEditor {
194
195        public TaggingPresetSourceEditor() {
196            super(SourceType.TAGGING_PRESET, Config.getUrls().getJOSMWebsite()+"/presets", presetSourceProviders, true);
197        }
198
199        @Override
200        public Collection<? extends SourceEntry> getInitialSourcesList() {
201            return PresetPrefHelper.INSTANCE.get();
202        }
203
204        @Override
205        public boolean finish() {
206            return doFinish(PresetPrefHelper.INSTANCE, TaggingPresets.ICON_SOURCES.getKey());
207        }
208
209        @Override
210        public Collection<ExtendedSourceEntry> getDefault() {
211            return PresetPrefHelper.INSTANCE.getDefault();
212        }
213
214        @Override
215        public Collection<String> getInitialIconPathsList() {
216            return TaggingPresets.ICON_SOURCES.get();
217        }
218
219        @Override
220        public String getStr(I18nString ident) {
221            switch (ident) {
222            case AVAILABLE_SOURCES:
223                return tr("Available presets:");
224            case ACTIVE_SOURCES:
225                return tr("Active presets:");
226            case NEW_SOURCE_ENTRY_TOOLTIP:
227                return tr("Add a new preset by entering filename or URL");
228            case NEW_SOURCE_ENTRY:
229                return tr("New preset entry:");
230            case REMOVE_SOURCE_TOOLTIP:
231                return tr("Remove the selected presets from the list of active presets");
232            case EDIT_SOURCE_TOOLTIP:
233                return tr("Edit the filename or URL for the selected active preset");
234            case ACTIVATE_TOOLTIP:
235                return tr("Add the selected available presets to the list of active presets");
236            case RELOAD_ALL_AVAILABLE:
237                return marktr("Reloads the list of available presets from ''{0}''");
238            case LOADING_SOURCES_FROM:
239                return marktr("Loading preset sources from ''{0}''");
240            case FAILED_TO_LOAD_SOURCES_FROM:
241                return marktr("<html>Failed to load the list of preset sources from<br>"
242                        + "''{0}''.<br>"
243                        + "<br>"
244                        + "Details (untranslated):<br>{1}</html>");
245            case FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC:
246                return "/Preferences/Presets#FailedToLoadPresetSources";
247            case ILLEGAL_FORMAT_OF_ENTRY:
248                return marktr("Warning: illegal format of entry in preset list ''{0}''. Got ''{1}''");
249            default: throw new AssertionError();
250            }
251        }
252    }
253
254    @Override
255    public boolean ok() {
256        TaggingPreset.USE_VALIDATOR.put(useValidator.isSelected());
257        if (sources.finish() || TaggingPresets.SORT_MENU.put(sortMenu.isSelected())) {
258            TaggingPresets.destroy();
259            TaggingPresets.initialize();
260        }
261
262        return false;
263    }
264
265    @Override
266    public boolean isExpert() {
267        return false;
268    }
269
270    @Override
271    public String getHelpContext() {
272        return HelpUtil.ht("/Preferences/TaggingPresetPreference");
273    }
274}