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    }