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