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