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