// 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.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.BorderFactory;
import javax.swing.JCheckBox;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.gui.ExtendedDialog;
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.PreferenceTabbedPane.ValidationListener;
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.gui.tagging.presets.TaggingPresetReader;
import org.openstreetmap.josm.tools.GBC;
import org.openstreetmap.josm.tools.Utils;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* Preference settings for tagging presets.
*/
public final class TaggingPresetPreference implements SubPreferenceSetting {
private final class TaggingPresetValidationListener implements ValidationListener {
@Override
public boolean validatePreferences() {
if (sources.hasActiveSourcesChanged()) {
List<Integer> sourcesToRemove = new ArrayList<>();
int i = -1;
SOURCES:
for (SourceEntry source: sources.getActiveSources()) {
i++;
boolean canLoad = false;
try {
TaggingPresetReader.readAll(source.url, false);
canLoad = true;
} catch (IOException e) {
Main.warn(e, tr("Could not read tagging preset source: {0}", source));
ExtendedDialog ed = new ExtendedDialog(Main.parent, tr("Error"),
new String[] {tr("Yes"), tr("No"), tr("Cancel")});
ed.setContent(tr("Could not read tagging preset source: {0}\nDo you want to keep it?", source));
switch (ed.showDialog().getValue()) {
case 1:
continue SOURCES;
case 2:
sourcesToRemove.add(i);
continue SOURCES;
default:
return false;
}
} catch (SAXException e) {
// We will handle this in step with validation
Main.trace(e);
}
String errorMessage = null;
try {
TaggingPresetReader.readAll(source.url, true);
} catch (IOException e) {
// Should not happen, but at least show message
String msg = tr("Could not read tagging preset source {0}", source);
Main.error(e, msg);
JOptionPane.showMessageDialog(Main.parent, msg);
return false;
} catch (SAXParseException e) {
if (canLoad) {
errorMessage = tr("<html>Tagging preset source {0} can be loaded but it contains errors. " +
"Do you really want to use it?<br><br><table width=600>Error is: [{1}:{2}] {3}</table></html>",
source, e.getLineNumber(), e.getColumnNumber(), Utils.escapeReservedCharactersHTML(e.getMessage()));
} else {
errorMessage = tr("<html>Unable to parse tagging preset source: {0}. " +
"Do you really want to use it?<br><br><table width=400>Error is: [{1}:{2}] {3}</table></html>",
source, e.getLineNumber(), e.getColumnNumber(), Utils.escapeReservedCharactersHTML(e.getMessage()));
}
Main.warn(e, errorMessage);
} catch (SAXException e) {
if (canLoad) {
errorMessage = tr("<html>Tagging preset source {0} can be loaded but it contains errors. " +
"Do you really want to use it?<br><br><table width=600>Error is: {1}</table></html>",
source, Utils.escapeReservedCharactersHTML(e.getMessage()));
} else {
errorMessage = tr("<html>Unable to parse tagging preset source: {0}. " +
"Do you really want to use it?<br><br><table width=600>Error is: {1}</table></html>",
source, Utils.escapeReservedCharactersHTML(e.getMessage()));
}
Main.warn(e, errorMessage);
}
if (errorMessage != null) {
Main.error(errorMessage);
int result = JOptionPane.showConfirmDialog(Main.parent, new JLabel(errorMessage), tr("Error"),
JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.ERROR_MESSAGE);
switch (result) {
case JOptionPane.YES_OPTION:
continue SOURCES;
case JOptionPane.NO_OPTION:
sourcesToRemove.add(i);
continue SOURCES;
default:
return false;
}
}
}
sources.removeSources(sourcesToRemove);
return true;
} else {
return true;
}
}
}
/**
* Factory used to create a new {@code TaggingPresetPreference}.
*/
public static class Factory implements PreferenceSettingFactory {
@Override
public PreferenceSetting createPreferenceSetting() {
return new TaggingPresetPreference();
}
}
private TaggingPresetPreference() {
super();
}
private static final List<SourceProvider> presetSourceProviders = new ArrayList<>();
private SourceEditor sources;
private JCheckBox sortMenu;
/**
* Registers a new additional preset source provider.
* @param provider The preset source provider
* @return {@code true}, if the provider has been added, {@code false} otherwise
*/
public static boolean registerSourceProvider(SourceProvider provider) {
if (provider != null)
return presetSourceProviders.add(provider);
return false;
}
private final ValidationListener validationListener = new TaggingPresetValidationListener();
@Override
public void addGui(PreferenceTabbedPane gui) {
sortMenu = new JCheckBox(tr("Sort presets menu alphabetically"),
Main.pref.getBoolean("taggingpreset.sortmenu", false));
final JPanel panel = new JPanel(new GridBagLayout());
panel.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
panel.add(sortMenu, GBC.eol().insets(5, 5, 5, 0));
sources = new TaggingPresetSourceEditor();
panel.add(sources, GBC.eol().fill(GBC.BOTH));
final MapPreference mapPref = gui.getMapPreference();
mapPref.addSubTab(this, tr("Tagging Presets"), panel);
sources.deferLoading(mapPref, panel);
gui.addValidationListener(validationListener);
}
static class TaggingPresetSourceEditor extends SourceEditor {
private static final String ICONPREF = "taggingpreset.icon.sources";
TaggingPresetSourceEditor() {
super(SourceType.TAGGING_PRESET, Main.getJOSMWebsite()+"/presets", presetSourceProviders, true);
}
@Override
public Collection<? extends SourceEntry> getInitialSourcesList() {
return PresetPrefHelper.INSTANCE.get();
}
@Override
public boolean finish() {
return doFinish(PresetPrefHelper.INSTANCE, ICONPREF);
}
@Override
public Collection<ExtendedSourceEntry> getDefault() {
return PresetPrefHelper.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 presets:");
case ACTIVE_SOURCES:
return tr("Active presets:");
case NEW_SOURCE_ENTRY_TOOLTIP:
return tr("Add a new preset by entering filename or URL");
case NEW_SOURCE_ENTRY:
return tr("New preset entry:");
case REMOVE_SOURCE_TOOLTIP:
return tr("Remove the selected presets from the list of active presets");
case EDIT_SOURCE_TOOLTIP:
return tr("Edit the filename or URL for the selected active preset");
case ACTIVATE_TOOLTIP:
return tr("Add the selected available presets to the list of active presets");
case RELOAD_ALL_AVAILABLE:
return marktr("Reloads the list of available presets from ''{0}''");
case LOADING_SOURCES_FROM:
return marktr("Loading preset sources from ''{0}''");
case FAILED_TO_LOAD_SOURCES_FROM:
return marktr("<html>Failed to load the list of preset sources from<br>"
+ "''{0}''.<br>"
+ "<br>"
+ "Details (untranslated):<br>{1}</html>");
case FAILED_TO_LOAD_SOURCES_FROM_HELP_TOPIC:
return "/Preferences/Presets#FailedToLoadPresetSources";
case ILLEGAL_FORMAT_OF_ENTRY:
return marktr("Warning: illegal format of entry in preset list ''{0}''. Got ''{1}''");
default: throw new AssertionError();
}
}
}
@Override
public boolean ok() {
boolean restart = Main.pref.put("taggingpreset.sortmenu", sortMenu.getSelectedObjects() != null);
restart |= sources.finish();
return restart;
}
/**
* Helper class for tagging presets preferences.
*/
public static class PresetPrefHelper extends SourceEditor.SourcePrefHelper {
/**
* The unique instance.
*/
public static final PresetPrefHelper INSTANCE = new PresetPrefHelper();
/**
* Constructs a new {@code PresetPrefHelper}.
*/
public PresetPrefHelper() {
super("taggingpreset.entries");
}
@Override
public Collection<ExtendedSourceEntry> getDefault() {
ExtendedSourceEntry i = new ExtendedSourceEntry("defaultpresets.xml", "resource://data/defaultpresets.xml");
i.title = tr("Internal Preset");
i.description = tr("The default preset for JOSM");
return Collections.singletonList(i);
}
@Override
public Map<String, String> serialize(SourceEntry entry) {
Map<String, String> res = new HashMap<>();
res.put("url", entry.url);
res.put("title", entry.title == null ? "" : entry.title);
return res;
}
@Override
public SourceEntry deserialize(Map<String, String> s) {
return new SourceEntry(s.get("url"), null, s.get("title"), true);
}
}
@Override
public boolean isExpert() {
return false;
}
@Override
public TabPreferenceSetting getTabPreferenceSetting(final PreferenceTabbedPane gui) {
return gui.getMapPreference();
}
}