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