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