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