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