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}