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