001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.preferences.imagery;
003    
004    import static org.openstreetmap.josm.tools.I18n.marktr;
005    import static org.openstreetmap.josm.tools.I18n.tr;
006    
007    import java.awt.Color;
008    import java.awt.Component;
009    import java.awt.Dimension;
010    import java.awt.FlowLayout;
011    import java.awt.Font;
012    import java.awt.GridBagConstraints;
013    import java.awt.GridBagLayout;
014    import java.awt.event.ActionEvent;
015    import java.awt.event.ActionListener;
016    import java.awt.event.MouseEvent;
017    import java.io.IOException;
018    import java.net.MalformedURLException;
019    import java.net.URL;
020    import java.util.ArrayList;
021    import java.util.HashMap;
022    import java.util.HashSet;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Set;
026    
027    import javax.swing.AbstractAction;
028    import javax.swing.BorderFactory;
029    import javax.swing.Box;
030    import javax.swing.JButton;
031    import javax.swing.JEditorPane;
032    import javax.swing.JLabel;
033    import javax.swing.JOptionPane;
034    import javax.swing.JPanel;
035    import javax.swing.JScrollPane;
036    import javax.swing.JSeparator;
037    import javax.swing.JTabbedPane;
038    import javax.swing.JTable;
039    import javax.swing.JToolBar;
040    import javax.swing.event.ListSelectionEvent;
041    import javax.swing.event.ListSelectionListener;
042    import javax.swing.event.TableModelEvent;
043    import javax.swing.event.TableModelListener;
044    import javax.swing.table.DefaultTableCellRenderer;
045    import javax.swing.table.DefaultTableModel;
046    import javax.swing.table.TableColumnModel;
047    
048    import org.openstreetmap.gui.jmapviewer.Coordinate;
049    import org.openstreetmap.gui.jmapviewer.JMapViewer;
050    import org.openstreetmap.gui.jmapviewer.MapPolygonImpl;
051    import org.openstreetmap.gui.jmapviewer.MapRectangleImpl;
052    import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
053    import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
054    import org.openstreetmap.josm.Main;
055    import org.openstreetmap.josm.data.imagery.ImageryInfo;
056    import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
057    import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
058    import org.openstreetmap.josm.data.imagery.OffsetBookmark;
059    import org.openstreetmap.josm.data.imagery.Shape;
060    import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
061    import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
062    import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
063    import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
064    import org.openstreetmap.josm.gui.util.GuiHelper;
065    import org.openstreetmap.josm.tools.GBC;
066    import org.openstreetmap.josm.tools.ImageProvider;
067    import org.openstreetmap.josm.tools.LanguageInfo;
068    
069    public class ImageryPreference extends DefaultTabPreferenceSetting {
070        public static class Factory implements PreferenceSettingFactory {
071            @Override
072            public PreferenceSetting createPreferenceSetting() {
073                return new ImageryPreference();
074            }
075        }
076    
077        private ImageryPreference() {
078            super("imagery", tr("Imagery Preferences"), tr("Modify list of imagery layers displayed in the Imagery menu"));
079        }
080    
081        private ImageryProvidersPanel imageryProviders;
082        private ImageryLayerInfo layerInfo;
083    
084        private CommonSettingsPanel commonSettings;
085        private WMSSettingsPanel wmsSettings;
086        private TMSSettingsPanel tmsSettings;
087    
088        private void addSettingsSection(final JPanel p, String name, JPanel section) {
089            addSettingsSection(p, name, section, GBC.eol());
090        }
091        
092        private void addSettingsSection(final JPanel p, String name, JPanel section, GBC gbc) {
093            final JLabel lbl = new JLabel(name);
094            lbl.setFont(lbl.getFont().deriveFont(Font.BOLD));
095            p.add(lbl,GBC.std());
096            p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
097            p.add(section, gbc.insets(20,5,0,10));
098        }
099    
100        private Component buildSettingsPanel(final PreferenceTabbedPane gui) {
101            final JPanel p = new JPanel(new GridBagLayout());
102            p.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
103    
104            addSettingsSection(p, tr("Common Settings"), commonSettings = new CommonSettingsPanel());
105            addSettingsSection(p, tr("WMS Settings"), wmsSettings = new WMSSettingsPanel(),
106                    GBC.eol().fill(GBC.HORIZONTAL));
107            addSettingsSection(p, tr("TMS Settings"), tmsSettings = new TMSSettingsPanel(),
108                    GBC.eol().fill(GBC.HORIZONTAL));
109    
110            p.add(new JPanel(),GBC.eol().fill(GBC.BOTH));
111            return new JScrollPane(p);
112        }
113    
114        @Override
115        public void addGui(final PreferenceTabbedPane gui) {
116            JPanel p = gui.createPreferenceTab(this);
117            JTabbedPane pane = new JTabbedPane();
118            layerInfo = new ImageryLayerInfo(ImageryLayerInfo.instance);
119            imageryProviders = new ImageryProvidersPanel(gui, layerInfo);
120            pane.add(imageryProviders);
121            pane.add(buildSettingsPanel(gui));
122            pane.add(new OffsetBookmarksPanel(gui));
123            loadSettings();
124            pane.setTitleAt(0, tr("Imagery providers"));
125            pane.setTitleAt(1, tr("Settings"));
126            pane.setTitleAt(2, tr("Offset bookmarks"));
127            p.add(pane,GBC.std().fill(GBC.BOTH));
128        }
129    
130        public ImageryProvidersPanel getProvidersPanel() {
131            return imageryProviders;
132        }
133    
134        private void loadSettings() {
135            commonSettings.loadSettings();
136            wmsSettings.loadSettings();
137            tmsSettings.loadSettings();
138        }
139    
140        @Override
141        public boolean ok() {
142            layerInfo.save();
143            ImageryLayerInfo.instance.clear();
144            ImageryLayerInfo.instance.load();
145            Main.main.menu.imageryMenu.refreshImageryMenu();
146            Main.main.menu.imageryMenu.refreshOffsetMenu();
147            OffsetBookmark.saveBookmarks();
148    
149            boolean commonRestartRequired = commonSettings.saveSettings();
150            boolean wmsRestartRequired = wmsSettings.saveSettings();
151            boolean tmsRestartRequired = tmsSettings.saveSettings();
152    
153            return commonRestartRequired || wmsRestartRequired || tmsRestartRequired;
154        }
155    
156        /**
157         * Updates a server URL in the preferences dialog. Used by plugins.
158         *
159         * @param server
160         *            The server name
161         * @param url
162         *            The server URL
163         */
164        public void setServerUrl(String server, String url) {
165            for (int i = 0; i < imageryProviders.activeModel.getRowCount(); i++) {
166                if (server.equals(imageryProviders.activeModel.getValueAt(i, 0).toString())) {
167                    imageryProviders.activeModel.setValueAt(url, i, 1);
168                    return;
169                }
170            }
171            imageryProviders.activeModel.addRow(new String[] { server, url });
172        }
173    
174        /**
175         * Gets a server URL in the preferences dialog. Used by plugins.
176         *
177         * @param server
178         *            The server name
179         * @return The server URL
180         */
181        public String getServerUrl(String server) {
182            for (int i = 0; i < imageryProviders.activeModel.getRowCount(); i++) {
183                if (server.equals(imageryProviders.activeModel.getValueAt(i, 0).toString()))
184                    return imageryProviders.activeModel.getValueAt(i, 1).toString();
185            }
186            return null;
187        }
188    
189        public static class ImageryProvidersPanel extends JPanel {
190            // Public JTables and JMapViewer
191            public final JTable activeTable;
192            public final JTable defaultTable;
193            public final JMapViewer defaultMap;
194    
195            // Public models
196            public final ImageryLayerTableModel activeModel;
197            public final ImageryDefaultLayerTableModel defaultModel;
198    
199            // Public JToolbars
200            public final JToolBar activeToolbar;
201            public final JToolBar middleToolbar;
202            public final JToolBar defaultToolbar;
203    
204            // Private members
205            private final PreferenceTabbedPane gui;
206            private final ImageryLayerInfo layerInfo;
207    
208            private static class ImageryTableCellRenderer extends DefaultTableCellRenderer {
209    
210                private List<ImageryInfo> layers;
211    
212                public ImageryTableCellRenderer(List<ImageryInfo> layers) {
213                    this.layers = layers;
214                }
215    
216                @Override
217                public Component getTableCellRendererComponent(JTable table, Object value, boolean
218                        isSelected, boolean hasFocus, int row, int column) {
219                    JLabel label = (JLabel) super.getTableCellRendererComponent(
220                            table, value, isSelected, hasFocus, row, column);
221                    label.setBackground(Main.pref.getUIColor("Table.background"));
222                    if (isSelected) {
223                        label.setForeground(Main.pref.getUIColor("Table.foreground"));
224                    }
225                    if (value != null) { // Fix #8159
226                        String t = value.toString();
227                        for (ImageryInfo l : layers) {
228                            if (l.getExtendedUrl().equals(t)) {
229                                label.setBackground(Main.pref.getColor(
230                                        marktr("Imagery Background: Default"),
231                                        new Color(200,255,200)));
232                                break;
233                            }
234                        }
235                    }
236                    return label;
237                }
238            }
239    
240            public ImageryProvidersPanel(final PreferenceTabbedPane gui, ImageryLayerInfo layerInfoArg) {
241                super(new GridBagLayout());
242                this.gui = gui;
243                this.layerInfo = layerInfoArg;
244                this.activeModel = new ImageryLayerTableModel();
245    
246                activeTable = new JTable(activeModel) {
247                    @Override
248                    public String getToolTipText(MouseEvent e) {
249                        java.awt.Point p = e.getPoint();
250                        return activeModel.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
251                    }
252                };
253    
254                defaultModel = new ImageryDefaultLayerTableModel();
255                defaultTable = new JTable(defaultModel) {
256                    @Override
257                    public String getToolTipText(MouseEvent e) {
258                        java.awt.Point p = e.getPoint();
259                        return (String) defaultModel.getValueAt(rowAtPoint(p), columnAtPoint(p));
260                    }
261                };
262    
263                defaultModel.addTableModelListener(
264                        new TableModelListener() {
265                            @Override
266                            public void tableChanged(TableModelEvent e) {
267                                activeTable.repaint();
268                            }
269                        }
270                        );
271    
272                activeModel.addTableModelListener(
273                        new TableModelListener() {
274                            @Override
275                            public void tableChanged(TableModelEvent e) {
276                                defaultTable.repaint();
277                            }
278                        }
279                        );
280    
281                TableColumnModel mod = defaultTable.getColumnModel();
282                mod.getColumn(2).setPreferredWidth(800);
283                mod.getColumn(2).setCellRenderer(new ImageryTableCellRenderer(layerInfo.getLayers()));
284                mod.getColumn(1).setPreferredWidth(400);
285                mod.getColumn(0).setPreferredWidth(50);
286    
287                mod = activeTable.getColumnModel();
288                mod.getColumn(1).setPreferredWidth(800);
289                mod.getColumn(1).setCellRenderer(new ImageryTableCellRenderer(layerInfo.getDefaultLayers()));
290                mod.getColumn(0).setPreferredWidth(200);
291    
292                RemoveEntryAction remove = new RemoveEntryAction();
293                activeTable.getSelectionModel().addListSelectionListener(remove);
294    
295                add(new JLabel(tr("Available default entries:")), GBC.eol().insets(5, 5, 0, 0));
296                // Add default item list
297                JScrollPane scrolldef = new JScrollPane(defaultTable);
298                scrolldef.setPreferredSize(new Dimension(200, 200));
299                add(scrolldef, GBC.std().insets(0, 5, 0, 0).fill(GridBagConstraints.BOTH).weight(1.0, 0.6).insets(5, 0, 0, 0));
300    
301                // Add default item map
302                defaultMap = new JMapViewer();
303                defaultMap.setZoomContolsVisible(false);
304                defaultMap.setMinimumSize(new Dimension(100, 200));
305                add(defaultMap, GBC.std().insets(5, 5, 0, 0).fill(GridBagConstraints.BOTH).weight(0.33, 0.6).insets(5, 0, 0, 0));
306    
307                defaultTable.getSelectionModel().addListSelectionListener(new DefListSelectionListener());
308    
309                defaultToolbar = new JToolBar(JToolBar.VERTICAL);
310                defaultToolbar.setFloatable(false);
311                defaultToolbar.setBorderPainted(false);
312                defaultToolbar.setOpaque(false);
313                defaultToolbar.add(new ReloadAction());
314                add(defaultToolbar, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 5, 0));
315    
316                ActivateAction activate = new ActivateAction();
317                defaultTable.getSelectionModel().addListSelectionListener(activate);
318                JButton btnActivate = new JButton(activate);
319    
320                middleToolbar = new JToolBar(JToolBar.HORIZONTAL);
321                middleToolbar.setFloatable(false);
322                middleToolbar.setBorderPainted(false);
323                middleToolbar.setOpaque(false);
324                middleToolbar.add(btnActivate);
325                add(middleToolbar, GBC.eol().anchor(GBC.CENTER).insets(5, 15, 5, 0));
326    
327                add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
328    
329                add(new JLabel(tr("Selected entries:")), GBC.eol().insets(5, 0, 0, 0));
330                JScrollPane scroll = new JScrollPane(activeTable);
331                add(scroll, GBC.std().fill(GridBagConstraints.BOTH).span(GridBagConstraints.RELATIVE).weight(1.0, 0.4).insets(5, 0, 0, 5));
332                scroll.setPreferredSize(new Dimension(200, 200));
333    
334                activeToolbar = new JToolBar(JToolBar.VERTICAL);
335                activeToolbar.setFloatable(false);
336                activeToolbar.setBorderPainted(false);
337                activeToolbar.setOpaque(false);
338                activeToolbar.add(new NewEntryAction());
339                //activeToolbar.add(edit); TODO
340                activeToolbar.add(remove);
341                add(activeToolbar, GBC.eol().anchor(GBC.NORTH).insets(0, 0, 5, 5));
342    
343            }
344    
345            // Listener of default providers list selection
346            private final class DefListSelectionListener implements ListSelectionListener {
347                // The current drawn rectangles and polygons
348                private final Map<Integer, MapRectangle> mapRectangles;
349                private final Map<Integer, List<MapPolygon>> mapPolygons;
350    
351                private DefListSelectionListener() {
352                    this.mapRectangles = new HashMap<Integer, MapRectangle>();
353                    this.mapPolygons = new HashMap<Integer, List<MapPolygon>>();
354                }
355    
356                @Override
357                public void valueChanged(ListSelectionEvent e) {
358                    // First index is set to -1 when the list is refreshed, so discard all map rectangles and polygons
359                    if (e.getFirstIndex() == -1) {
360                        defaultMap.removeAllMapRectangles();
361                        defaultMap.removeAllMapPolygons();
362                        mapRectangles.clear();
363                        mapPolygons.clear();
364                        // Only process complete (final) selection events
365                    } else if (!e.getValueIsAdjusting()) {
366                        for (int i = e.getFirstIndex(); i<=e.getLastIndex(); i++) {
367                            updateBoundsAndShapes(i);
368                        }
369                        // If needed, adjust map to show all map rectangles and polygons
370                        if (!mapRectangles.isEmpty() || !mapPolygons.isEmpty()) {
371                            defaultMap.setDisplayToFitMapElements(false, true, true);
372                            defaultMap.zoomOut();
373                        }
374                    }
375                }
376    
377                private void updateBoundsAndShapes(int i) {
378                    ImageryBounds bounds = defaultModel.getRow(i).getBounds();
379                    if (bounds != null) {
380                        List<Shape> shapes = bounds.getShapes();
381                        if (shapes != null && !shapes.isEmpty()) {
382                            if (defaultTable.getSelectionModel().isSelectedIndex(i)) {
383                                if (!mapPolygons.containsKey(i)) {
384                                    List<MapPolygon> list = new ArrayList<MapPolygon>();
385                                    mapPolygons.put(i, list);
386                                    // Add new map polygons
387                                    for (Shape shape : shapes) {
388                                        MapPolygon polygon = new MapPolygonImpl(shape.getPoints());
389                                        list.add(polygon);
390                                        defaultMap.addMapPolygon(polygon);
391                                    }
392                                }
393                            } else if (mapPolygons.containsKey(i)) {
394                                // Remove previously drawn map polygons
395                                for (MapPolygon polygon : mapPolygons.get(i)) {
396                                    defaultMap.removeMapPolygon(polygon);
397                                }
398                                mapPolygons.remove(i);
399                            }
400                            // Only display bounds when no polygons (shapes) are defined for this provider
401                        } else {
402                            if (defaultTable.getSelectionModel().isSelectedIndex(i)) {
403                                if (!mapRectangles.containsKey(i)) {
404                                    // Add new map rectangle
405                                    Coordinate topLeft = new Coordinate(bounds.getMax().lat(), bounds.getMin().lon());
406                                    Coordinate bottomRight = new Coordinate(bounds.getMin().lat(), bounds.getMax().lon());
407                                    MapRectangle rectangle = new MapRectangleImpl(topLeft, bottomRight);
408                                    mapRectangles.put(i, rectangle);
409                                    defaultMap.addMapRectangle(rectangle);
410                                }
411                            } else if (mapRectangles.containsKey(i)) {
412                                // Remove previously drawn map rectangle
413                                defaultMap.removeMapRectangle(mapRectangles.get(i));
414                                mapRectangles.remove(i);
415                            }
416                        }
417                    }
418                }
419            }
420    
421            private class NewEntryAction extends AbstractAction {
422                public NewEntryAction() {
423                    putValue(NAME, tr("New"));
424                    putValue(SHORT_DESCRIPTION, tr("Add a new WMS/TMS entry by entering the URL"));
425                    putValue(SMALL_ICON, ImageProvider.get("dialogs", "add"));
426                }
427    
428                public void actionPerformed(ActionEvent evt) {
429                    final AddWMSLayerPanel p = new AddWMSLayerPanel();
430                    GuiHelper.prepareResizeableOptionPane(p, new Dimension(250, 350));
431                    int answer = JOptionPane.showConfirmDialog(
432                            gui, p, tr("Add Imagery URL"),
433                            JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE);
434                    if (answer == JOptionPane.OK_OPTION) {
435                        try {
436                            activeModel.addRow(p.getImageryInfo());
437                        } catch (IllegalArgumentException ex) {
438                            if (ex.getMessage() == null || ex.getMessage().isEmpty())
439                                throw ex;
440                            else {
441                                JOptionPane.showMessageDialog(Main.parent,
442                                        ex.getMessage(), tr("Error"),
443                                        JOptionPane.ERROR_MESSAGE);
444                            }
445                        }
446                    }
447                }
448            }
449    
450            private class RemoveEntryAction extends AbstractAction implements ListSelectionListener {
451    
452                public RemoveEntryAction() {
453                    putValue(NAME, tr("Remove"));
454                    putValue(SHORT_DESCRIPTION, tr("Remove entry"));
455                    putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete"));
456                    updateEnabledState();
457                }
458    
459                protected void updateEnabledState() {
460                    setEnabled(activeTable.getSelectedRowCount() > 0);
461                }
462    
463                @Override
464                public void valueChanged(ListSelectionEvent e) {
465                    updateEnabledState();
466                }
467    
468                @Override
469                public void actionPerformed(ActionEvent e) {
470                    Integer i;
471                    while ((i = activeTable.getSelectedRow()) != -1) {
472                        activeModel.removeRow(i);
473                    }
474                }
475            }
476    
477            private class ActivateAction extends AbstractAction implements ListSelectionListener {
478                public ActivateAction() {
479                    putValue(NAME, tr("Activate"));
480                    putValue(SHORT_DESCRIPTION, tr("copy selected defaults"));
481                    putValue(SMALL_ICON, ImageProvider.get("preferences", "activate-down"));
482                }
483    
484                protected void updateEnabledState() {
485                    setEnabled(defaultTable.getSelectedRowCount() > 0);
486                }
487    
488                @Override
489                public void valueChanged(ListSelectionEvent e) {
490                    updateEnabledState();
491                }
492    
493                @Override
494                public void actionPerformed(ActionEvent e) {
495                    int[] lines = defaultTable.getSelectedRows();
496                    if (lines.length == 0) {
497                        JOptionPane.showMessageDialog(
498                                gui,
499                                tr("Please select at least one row to copy."),
500                                tr("Information"),
501                                JOptionPane.INFORMATION_MESSAGE);
502                        return;
503                    }
504    
505                    Set<String> acceptedEulas = new HashSet<String>();
506    
507                    outer: for (int i = 0; i < lines.length; i++) {
508                        ImageryInfo info = defaultModel.getRow(lines[i]);
509    
510                        // Check if an entry with exactly the same values already
511                        // exists
512                        for (int j = 0; j < activeModel.getRowCount(); j++) {
513                            if (info.equalsBaseValues(activeModel.getRow(j))) {
514                                // Select the already existing row so the user has
515                                // some feedback in case an entry exists
516                                activeTable.getSelectionModel().setSelectionInterval(j, j);
517                                activeTable.scrollRectToVisible(activeTable.getCellRect(j, 0, true));
518                                continue outer;
519                            }
520                        }
521    
522                        String eulaURL = info.getEulaAcceptanceRequired();
523                        // If set and not already accepted, ask for EULA acceptance
524                        if (eulaURL != null && !acceptedEulas.contains(eulaURL)) {
525                            if (confirmEulaAcceptance(gui, eulaURL)) {
526                                acceptedEulas.add(eulaURL);
527                            } else {
528                                continue outer;
529                            }
530                        }
531    
532                        activeModel.addRow(new ImageryInfo(info));
533                        int lastLine = activeModel.getRowCount() - 1;
534                        activeTable.getSelectionModel().setSelectionInterval(lastLine, lastLine);
535                        activeTable.scrollRectToVisible(activeTable.getCellRect(lastLine, 0, true));
536                    }
537                }
538            }
539    
540            private class ReloadAction extends AbstractAction {
541                public ReloadAction() {
542                    putValue(SHORT_DESCRIPTION, tr("reload defaults"));
543                    putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh"));
544                }
545    
546                public void actionPerformed(ActionEvent evt) {
547                    layerInfo.loadDefaults(true);
548                    defaultModel.fireTableDataChanged();
549                }
550            }
551    
552            /**
553             * The table model for imagery layer list
554             */
555            public class ImageryLayerTableModel extends DefaultTableModel {
556                public ImageryLayerTableModel() {
557                    setColumnIdentifiers(new String[] { tr("Menu Name"), tr("Imagery URL")});
558                }
559    
560                public ImageryInfo getRow(int row) {
561                    return layerInfo.getLayers().get(row);
562                }
563    
564                public void addRow(ImageryInfo i) {
565                    layerInfo.add(i);
566                    int p = getRowCount() - 1;
567                    fireTableRowsInserted(p, p);
568                }
569    
570                @Override
571                public void removeRow(int i) {
572                    layerInfo.remove(getRow(i));
573                    fireTableRowsDeleted(i, i);
574                }
575    
576                @Override
577                public int getRowCount() {
578                    return layerInfo.getLayers().size();
579                }
580    
581                @Override
582                public Object getValueAt(int row, int column) {
583                    ImageryInfo info = layerInfo.getLayers().get(row);
584                    switch (column) {
585                    case 0:
586                        return info.getName();
587                    case 1:
588                        return info.getExtendedUrl();
589                    default:
590                        throw new ArrayIndexOutOfBoundsException();
591                    }
592                }
593    
594                @Override
595                public void setValueAt(Object o, int row, int column) {
596                    if (layerInfo.getLayers().size() <= row) return;
597                    ImageryInfo info = layerInfo.getLayers().get(row);
598                    switch (column) {
599                    case 0:
600                        info.setName((String) o);
601                        break;
602                    case 1:
603                        info.setExtendedUrl((String)o);
604                        break;
605                    default:
606                        throw new ArrayIndexOutOfBoundsException();
607                    }
608                }
609    
610                @Override
611                public boolean isCellEditable(int row, int column) {
612                    return true;
613                }
614            }
615    
616            /**
617             * The table model for the default imagery layer list
618             */
619            public class ImageryDefaultLayerTableModel extends DefaultTableModel {
620                public ImageryDefaultLayerTableModel() {
621                    setColumnIdentifiers(new String[]{"", tr("Menu Name (Default)"), tr("Imagery URL (Default)")});
622                }
623    
624                public ImageryInfo getRow(int row) {
625                    return layerInfo.getDefaultLayers().get(row);
626                }
627    
628                @Override
629                public int getRowCount() {
630                    return layerInfo.getDefaultLayers().size();
631                }
632    
633                @Override
634                public Object getValueAt(int row, int column) {
635                    ImageryInfo info = layerInfo.getDefaultLayers().get(row);
636                    switch (column) {
637                    case 0:
638                        return info.getCountryCode();
639                    case 1:
640                        return info.getName();
641                    case 2:
642                        return info.getExtendedUrl();
643                    }
644                    return null;
645                }
646    
647                @Override
648                public boolean isCellEditable(int row, int column) {
649                    return false;
650                }
651            }
652    
653            private boolean confirmEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) {
654                URL url = null;
655                try {
656                    url = new URL(eulaUrl.replaceAll("\\{lang\\}", LanguageInfo.getWikiLanguagePrefix()));
657                    JEditorPane htmlPane = null;
658                    try {
659                        htmlPane = new JEditorPane(url);
660                    } catch (IOException e1) {
661                        // give a second chance with a default Locale 'en'
662                        try {
663                            url = new URL(eulaUrl.replaceAll("\\{lang\\}", ""));
664                            htmlPane = new JEditorPane(url);
665                        } catch (IOException e2) {
666                            JOptionPane.showMessageDialog(gui ,tr("EULA license URL not available: {0}", eulaUrl));
667                            return false;
668                        }
669                    }
670                    Box box = Box.createVerticalBox();
671                    htmlPane.setEditable(false);
672                    JScrollPane scrollPane = new JScrollPane(htmlPane);
673                    scrollPane.setPreferredSize(new Dimension(400, 400));
674                    box.add(scrollPane);
675                    int option = JOptionPane.showConfirmDialog(Main.parent, box, tr("Please abort if you are not sure"), JOptionPane.YES_NO_OPTION,
676                            JOptionPane.WARNING_MESSAGE);
677                    if (option == JOptionPane.YES_OPTION)
678                        return true;
679                } catch (MalformedURLException e2) {
680                    JOptionPane.showMessageDialog(gui ,tr("Malformed URL for the EULA licence: {0}", eulaUrl));
681                }
682                return false;
683            }
684        }
685    
686        static class OffsetBookmarksPanel extends JPanel {
687            List<OffsetBookmark> bookmarks = OffsetBookmark.allBookmarks;
688            OffsetsBookmarksModel model = new OffsetsBookmarksModel();
689    
690            public OffsetBookmarksPanel(final PreferenceTabbedPane gui) {
691                super(new GridBagLayout());
692                final JTable list = new JTable(model) {
693                    @Override
694                    public String getToolTipText(MouseEvent e) {
695                        java.awt.Point p = e.getPoint();
696                        return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
697                    }
698                };
699                JScrollPane scroll = new JScrollPane(list);
700                add(scroll, GBC.eol().fill(GridBagConstraints.BOTH));
701                scroll.setPreferredSize(new Dimension(200, 200));
702    
703                TableColumnModel mod = list.getColumnModel();
704                mod.getColumn(0).setPreferredWidth(150);
705                mod.getColumn(1).setPreferredWidth(200);
706                mod.getColumn(2).setPreferredWidth(300);
707                mod.getColumn(3).setPreferredWidth(150);
708                mod.getColumn(4).setPreferredWidth(150);
709    
710                JPanel buttonPanel = new JPanel(new FlowLayout());
711    
712                JButton add = new JButton(tr("Add"));
713                buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0));
714                add.addActionListener(new ActionListener() {
715                    @Override
716                    public void actionPerformed(ActionEvent e) {
717                        OffsetBookmark b = new OffsetBookmark(Main.getProjection().toCode(),"","",0,0);
718                        model.addRow(b);
719                    }
720                });
721    
722                JButton delete = new JButton(tr("Delete"));
723                buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0));
724                delete.addActionListener(new ActionListener() {
725                    @Override
726                    public void actionPerformed(ActionEvent e) {
727                        if (list.getSelectedRow() == -1) {
728                            JOptionPane.showMessageDialog(gui, tr("Please select the row to delete."));
729                        } else {
730                            Integer i;
731                            while ((i = list.getSelectedRow()) != -1) {
732                                model.removeRow(i);
733                            }
734                        }
735                    }
736                });
737    
738                add(buttonPanel,GBC.eol());
739            }
740    
741            /**
742             * The table model for imagery offsets list
743             */
744            class OffsetsBookmarksModel extends DefaultTableModel {
745                public OffsetsBookmarksModel() {
746                    setColumnIdentifiers(new String[] { tr("Projection"),  tr("Layer"), tr("Name"), tr("Easting"), tr("Northing"),});
747                }
748    
749                public OffsetBookmark getRow(int row) {
750                    return bookmarks.get(row);
751                }
752    
753                public void addRow(OffsetBookmark i) {
754                    bookmarks.add(i);
755                    int p = getRowCount() - 1;
756                    fireTableRowsInserted(p, p);
757                }
758    
759                @Override
760                public void removeRow(int i) {
761                    bookmarks.remove(getRow(i));
762                    fireTableRowsDeleted(i, i);
763                }
764    
765                @Override
766                public int getRowCount() {
767                    return bookmarks.size();
768                }
769    
770                @Override
771                public Object getValueAt(int row, int column) {
772                    OffsetBookmark info = bookmarks.get(row);
773                    switch (column) {
774                    case 0:
775                        if (info.projectionCode == null) return "";
776                        return info.projectionCode.toString();
777                    case 1:
778                        return info.layerName;
779                    case 2:
780                        return info.name;
781                    case 3:
782                        return info.dx;
783                    case 4:
784                        return info.dy;
785                    default:
786                        throw new ArrayIndexOutOfBoundsException();
787                    }
788                }
789    
790                @Override
791                public void setValueAt(Object o, int row, int column) {
792                    OffsetBookmark info = bookmarks.get(row);
793                    switch (column) {
794                    case 1:
795                        info.layerName = o.toString();
796                        break;
797                    case 2:
798                        info.name = o.toString();
799                        break;
800                    case 3:
801                        info.dx = Double.parseDouble((String) o);
802                        break;
803                    case 4:
804                        info.dy = Double.parseDouble((String) o);
805                        break;
806                    default:
807                        throw new ArrayIndexOutOfBoundsException();
808                    }
809                }
810    
811                @Override
812                public boolean isCellEditable(int row, int column) {
813                    return column >= 1;
814                }
815            }
816        }
817    
818        public static void initialize() {
819            ImageryLayerInfo.instance.clear();
820            ImageryLayerInfo.instance.loadDefaults(false);
821            ImageryLayerInfo.instance.load();
822            OffsetBookmark.loadBookmarks();
823            Main.main.menu.imageryMenu.refreshImageryMenu();
824            Main.main.menu.imageryMenu.refreshOffsetMenu();
825        }
826    }