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