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.GridBagConstraints;
011import java.awt.GridBagLayout;
012import java.awt.event.ActionEvent;
013import java.awt.event.MouseAdapter;
014import java.awt.event.MouseEvent;
015import java.io.IOException;
016import java.net.MalformedURLException;
017import java.net.URL;
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.Set;
025import java.util.function.BiConsumer;
026import java.util.function.Consumer;
027import java.util.function.Function;
028
029import javax.swing.AbstractAction;
030import javax.swing.Box;
031import javax.swing.JButton;
032import javax.swing.JLabel;
033import javax.swing.JOptionPane;
034import javax.swing.JPanel;
035import javax.swing.JScrollPane;
036import javax.swing.JTable;
037import javax.swing.JToolBar;
038import javax.swing.UIManager;
039import javax.swing.event.ListSelectionEvent;
040import javax.swing.event.ListSelectionListener;
041import javax.swing.table.DefaultTableCellRenderer;
042import javax.swing.table.DefaultTableModel;
043import javax.swing.table.TableColumnModel;
044
045import org.openstreetmap.gui.jmapviewer.Coordinate;
046import org.openstreetmap.gui.jmapviewer.MapPolygonImpl;
047import org.openstreetmap.gui.jmapviewer.MapRectangleImpl;
048import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon;
049import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle;
050import org.openstreetmap.josm.data.imagery.ImageryInfo;
051import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds;
052import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryCategory;
053import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
054import org.openstreetmap.josm.data.imagery.Shape;
055import org.openstreetmap.josm.data.preferences.NamedColorProperty;
056import org.openstreetmap.josm.gui.MainApplication;
057import org.openstreetmap.josm.gui.bbox.JosmMapViewer;
058import org.openstreetmap.josm.gui.bbox.SlippyMapBBoxChooser;
059import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
060import org.openstreetmap.josm.gui.util.GuiHelper;
061import org.openstreetmap.josm.gui.widgets.FilterField;
062import org.openstreetmap.josm.gui.widgets.HtmlPanel;
063import org.openstreetmap.josm.gui.widgets.JosmEditorPane;
064import org.openstreetmap.josm.spi.preferences.Config;
065import org.openstreetmap.josm.tools.GBC;
066import org.openstreetmap.josm.tools.ImageProvider;
067import org.openstreetmap.josm.tools.ImageProvider.ImageSizes;
068import org.openstreetmap.josm.tools.LanguageInfo;
069import org.openstreetmap.josm.tools.Logging;
070
071/**
072 * A panel displaying imagery providers.
073 * @since 15115 (extracted from ImageryPreferences)
074 */
075public class ImageryProvidersPanel extends JPanel {
076    // Public JTables and JosmMapViewer
077    /** The table of active providers **/
078    public final JTable activeTable;
079    /** The table of default providers **/
080    public final JTable defaultTable;
081    /** The filter of default providers **/
082    private final FilterField defaultFilter;
083    /** The selection listener synchronizing map display with table of default providers **/
084    private final transient DefListSelectionListener defaultTableListener;
085    /** The map displaying imagery bounds of selected default providers **/
086    public final JosmMapViewer defaultMap;
087
088    // Public models
089    /** The model of active providers **/
090    public final ImageryLayerTableModel activeModel;
091    /** The model of default providers **/
092    public final ImageryDefaultLayerTableModel defaultModel;
093
094    // Public JToolbars
095    /** The toolbar on the right of active providers **/
096    public final JToolBar activeToolbar;
097    /** The toolbar on the middle of the panel **/
098    public final JToolBar middleToolbar;
099    /** The toolbar on the right of default providers **/
100    public final JToolBar defaultToolbar;
101
102    // Private members
103    private final PreferenceTabbedPane gui;
104    private final transient ImageryLayerInfo layerInfo;
105
106    /**
107     * class to render the URL information of Imagery source
108     * @since 8065
109     */
110    private static class ImageryURLTableCellRenderer extends DefaultTableCellRenderer {
111
112        private static final NamedColorProperty IMAGERY_BACKGROUND_COLOR = new NamedColorProperty(
113                marktr("Imagery Background: Default"),
114                new Color(200, 255, 200));
115
116        private final transient List<ImageryInfo> layers;
117
118        ImageryURLTableCellRenderer(List<ImageryInfo> layers) {
119            this.layers = layers;
120        }
121
122        @Override
123        public Component getTableCellRendererComponent(JTable table, Object value, boolean
124                isSelected, boolean hasFocus, int row, int column) {
125            JLabel label = (JLabel) super.getTableCellRendererComponent(
126                    table, value, isSelected, hasFocus, row, column);
127            GuiHelper.setBackgroundReadable(label, UIManager.getColor("Table.background"));
128            if (value != null) { // Fix #8159
129                String t = value.toString();
130                for (ImageryInfo l : layers) {
131                    if (l.getExtendedUrl().equals(t)) {
132                        GuiHelper.setBackgroundReadable(label, IMAGERY_BACKGROUND_COLOR.get());
133                        break;
134                    }
135                }
136                label.setToolTipText((String) value);
137            }
138            return label;
139        }
140    }
141
142    /**
143     * class to render an information of Imagery source
144     * @param <T> type of information
145     */
146    private static class ImageryTableCellRenderer<T> extends DefaultTableCellRenderer {
147        private final Function<T, Object> mapper;
148        private final Function<T, String> tooltip;
149        private final BiConsumer<T, JLabel> decorator;
150
151        ImageryTableCellRenderer(Function<T, Object> mapper, Function<T, String> tooltip, BiConsumer<T, JLabel> decorator) {
152            this.mapper = mapper;
153            this.tooltip = tooltip;
154            this.decorator = decorator;
155        }
156
157        @Override
158        @SuppressWarnings("unchecked")
159        public final Component getTableCellRendererComponent(JTable table, Object value, boolean
160                isSelected, boolean hasFocus, int row, int column) {
161            T obj = (T) value;
162            JLabel label = (JLabel) super.getTableCellRendererComponent(
163                    table, mapper.apply(obj), isSelected, hasFocus, row, column);
164            GuiHelper.setBackgroundReadable(label,
165                    isSelected ? UIManager.getColor("Table.selectionBackground") : UIManager.getColor("Table.background"));
166            if (obj != null) {
167                label.setToolTipText(tooltip.apply(obj));
168                if (decorator != null) {
169                    decorator.accept(obj, label);
170                }
171            }
172            return label;
173        }
174    }
175
176    /**
177     * class to render the category information of Imagery source
178     */
179    private static class ImageryCategoryTableCellRenderer extends ImageryProvidersPanel.ImageryTableCellRenderer<ImageryCategory> {
180        ImageryCategoryTableCellRenderer() {
181            super(cat -> null, cat -> tr("Imagery category: {0}", cat.getDescription()),
182                  (cat, label) -> label.setIcon(cat.getIcon(ImageSizes.TABLE)));
183        }
184    }
185
186    /**
187     * class to render the country information of Imagery source
188     */
189    private static class ImageryCountryTableCellRenderer extends ImageryProvidersPanel.ImageryTableCellRenderer<String> {
190        ImageryCountryTableCellRenderer() {
191            super(code -> code, ImageryInfo::getLocalizedCountry, null);
192        }
193    }
194
195    /**
196     * class to render the name information of Imagery source
197     */
198    private static class ImageryNameTableCellRenderer extends ImageryProvidersPanel.ImageryTableCellRenderer<ImageryInfo> {
199        ImageryNameTableCellRenderer() {
200            super(info -> info == null ? null : info.getName(), ImageryInfo::getToolTipText, null);
201        }
202    }
203
204    /**
205     * Constructs a new {@code ImageryProvidersPanel}.
206     * @param gui The parent preference tab pane
207     * @param layerInfoArg The list of imagery entries to display
208     */
209    public ImageryProvidersPanel(final PreferenceTabbedPane gui, ImageryLayerInfo layerInfoArg) {
210        super(new GridBagLayout());
211        this.gui = gui;
212        this.layerInfo = layerInfoArg;
213        this.activeModel = new ImageryLayerTableModel();
214
215        activeTable = new JTable(activeModel) {
216            @Override
217            public String getToolTipText(MouseEvent e) {
218                java.awt.Point p = e.getPoint();
219                try {
220                    return activeModel.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
221                } catch (ArrayIndexOutOfBoundsException ex) {
222                    Logging.debug(ex);
223                    return null;
224                }
225            }
226        };
227        activeTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE);
228
229        defaultModel = new ImageryDefaultLayerTableModel();
230        defaultTable = new JTable(defaultModel);
231        defaultTable.setAutoCreateRowSorter(true);
232        defaultFilter = new FilterField().filter(defaultTable, defaultModel);
233
234        defaultModel.addTableModelListener(e -> activeTable.repaint());
235        activeModel.addTableModelListener(e -> defaultTable.repaint());
236
237        TableColumnModel mod = defaultTable.getColumnModel();
238        mod.getColumn(3).setPreferredWidth(775);
239        mod.getColumn(3).setCellRenderer(new ImageryURLTableCellRenderer(layerInfo.getLayers()));
240        mod.getColumn(2).setPreferredWidth(475);
241        mod.getColumn(2).setCellRenderer(new ImageryNameTableCellRenderer());
242        mod.getColumn(1).setCellRenderer(new ImageryCountryTableCellRenderer());
243        mod.getColumn(0).setPreferredWidth(50);
244        mod.getColumn(0).setCellRenderer(new ImageryCategoryTableCellRenderer());
245        mod.getColumn(0).setPreferredWidth(50);
246
247        mod = activeTable.getColumnModel();
248        mod.getColumn(1).setPreferredWidth(800);
249        mod.getColumn(1).setCellRenderer(new ImageryURLTableCellRenderer(layerInfo.getAllDefaultLayers()));
250        mod.getColumn(0).setPreferredWidth(200);
251
252        RemoveEntryAction remove = new RemoveEntryAction();
253        activeTable.getSelectionModel().addListSelectionListener(remove);
254
255        add(new JLabel(tr("Available default entries:")), GBC.std().insets(5, 5, 0, 0));
256        add(new JLabel(tr("Boundaries of selected imagery entries:")), GBC.eol().insets(5, 5, 0, 0));
257
258        // Add default item list
259        JPanel defaultPane = new JPanel(new GridBagLayout());
260        JScrollPane scrolldef = new JScrollPane(defaultTable);
261        scrolldef.setPreferredSize(new Dimension(200, 200));
262        defaultPane.add(defaultFilter, GBC.eol().insets(0, 0, 0, 0).fill(GridBagConstraints.HORIZONTAL));
263        defaultPane.add(scrolldef, GBC.eol().insets(0, 0, 0, 0).fill(GridBagConstraints.BOTH));
264        add(defaultPane, GBC.std().fill(GridBagConstraints.BOTH).weight(1.0, 0.6).insets(5, 0, 0, 0));
265
266        // Add default item map
267        defaultMap = new JosmMapViewer();
268        defaultMap.setTileSource(SlippyMapBBoxChooser.DefaultOsmTileSourceProvider.get()); // for attribution
269        defaultMap.addMouseListener(new MouseAdapter() {
270            @Override
271            public void mouseClicked(MouseEvent e) {
272                if (e.getButton() == MouseEvent.BUTTON1) {
273                    defaultMap.getAttribution().handleAttribution(e.getPoint(), true);
274                }
275            }
276        });
277        defaultMap.setZoomControlsVisible(false);
278        defaultMap.setMinimumSize(new Dimension(100, 200));
279        add(defaultMap, GBC.std().fill(GridBagConstraints.BOTH).weight(0.33, 0.6).insets(5, 0, 0, 0));
280
281        defaultTableListener = new DefListSelectionListener();
282        defaultTable.getSelectionModel().addListSelectionListener(defaultTableListener);
283
284        defaultToolbar = new JToolBar(JToolBar.VERTICAL);
285        defaultToolbar.setFloatable(false);
286        defaultToolbar.setBorderPainted(false);
287        defaultToolbar.setOpaque(false);
288        defaultToolbar.add(new ReloadAction());
289        add(defaultToolbar, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 5, 0));
290
291        HtmlPanel help = new HtmlPanel(tr("New default entries can be added in the <a href=\"{0}\">Wiki</a>.",
292            Config.getUrls().getJOSMWebsite()+"/wiki/Maps"));
293        help.enableClickableHyperlinks();
294        add(help, GBC.eol().insets(10, 0, 0, 0).fill(GBC.HORIZONTAL));
295
296        ActivateAction activate = new ActivateAction();
297        defaultTable.getSelectionModel().addListSelectionListener(activate);
298        JButton btnActivate = new JButton(activate);
299
300        middleToolbar = new JToolBar(JToolBar.HORIZONTAL);
301        middleToolbar.setFloatable(false);
302        middleToolbar.setBorderPainted(false);
303        middleToolbar.setOpaque(false);
304        middleToolbar.add(btnActivate);
305        add(middleToolbar, GBC.eol().anchor(GBC.CENTER).insets(5, 5, 5, 0));
306
307        add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL));
308
309        add(new JLabel(tr("Selected entries:")), GBC.eol().insets(5, 0, 0, 0));
310        JScrollPane scroll = new JScrollPane(activeTable);
311        add(scroll, GBC.std().fill(GridBagConstraints.BOTH).span(GridBagConstraints.RELATIVE).weight(1.0, 0.4).insets(5, 0, 0, 5));
312        scroll.setPreferredSize(new Dimension(200, 200));
313
314        activeToolbar = new JToolBar(JToolBar.VERTICAL);
315        activeToolbar.setFloatable(false);
316        activeToolbar.setBorderPainted(false);
317        activeToolbar.setOpaque(false);
318        activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMS));
319        activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.TMS));
320        activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMTS));
321        activeToolbar.add(remove);
322        add(activeToolbar, GBC.eol().anchor(GBC.NORTH).insets(0, 0, 5, 5));
323    }
324
325    // Listener of default providers list selection
326    private final class DefListSelectionListener implements ListSelectionListener {
327        // The current drawn rectangles and polygons
328        private final Map<Integer, MapRectangle> mapRectangles;
329        private final Map<Integer, List<MapPolygon>> mapPolygons;
330
331        private DefListSelectionListener() {
332            this.mapRectangles = new HashMap<>();
333            this.mapPolygons = new HashMap<>();
334        }
335
336        private void clearMap() {
337            defaultMap.removeAllMapRectangles();
338            defaultMap.removeAllMapPolygons();
339            mapRectangles.clear();
340            mapPolygons.clear();
341        }
342
343        @Override
344        public void valueChanged(ListSelectionEvent e) {
345            // First index can be set to -1 when the list is refreshed, so discard all map rectangles and polygons
346            if (e.getFirstIndex() == -1) {
347                clearMap();
348            } else if (!e.getValueIsAdjusting()) {
349                // Only process complete (final) selection events
350                for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) {
351                    if (i < defaultTable.getRowCount()) {
352                        updateBoundsAndShapes(defaultTable.convertRowIndexToModel(i));
353                    }
354                }
355                // Cleanup residual selected bounds which may have disappeared after a filter
356                cleanupResidualBounds();
357                // If needed, adjust map to show all map rectangles and polygons
358                if (!mapRectangles.isEmpty() || !mapPolygons.isEmpty()) {
359                    defaultMap.setDisplayToFitMapElements(false, true, true);
360                    defaultMap.zoomOut();
361                }
362            }
363        }
364
365        /**
366         * update bounds and shapes for a new entry
367         * @param i model index
368         */
369        private void updateBoundsAndShapes(int i) {
370            ImageryBounds bounds = defaultModel.getRow(i).getBounds();
371            if (bounds != null) {
372                int viewIndex = defaultTable.convertRowIndexToView(i);
373                List<Shape> shapes = bounds.getShapes();
374                if (shapes != null && !shapes.isEmpty()) {
375                    if (defaultTable.getSelectionModel().isSelectedIndex(viewIndex)) {
376                        if (!mapPolygons.containsKey(i)) {
377                            List<MapPolygon> list = new ArrayList<>();
378                            mapPolygons.put(i, list);
379                            // Add new map polygons
380                            for (Shape shape : shapes) {
381                                MapPolygon polygon = new MapPolygonImpl(shape.getPoints());
382                                list.add(polygon);
383                                defaultMap.addMapPolygon(polygon);
384                            }
385                        }
386                    } else if (mapPolygons.containsKey(i)) {
387                        // Remove previously drawn map polygons
388                        for (MapPolygon polygon : mapPolygons.get(i)) {
389                            defaultMap.removeMapPolygon(polygon);
390                        }
391                        mapPolygons.remove(i);
392                    }
393                    // Only display bounds when no polygons (shapes) are defined for this provider
394                } else {
395                    if (defaultTable.getSelectionModel().isSelectedIndex(viewIndex)) {
396                        if (!mapRectangles.containsKey(i)) {
397                            // Add new map rectangle
398                            Coordinate topLeft = new Coordinate(bounds.getMaxLat(), bounds.getMinLon());
399                            Coordinate bottomRight = new Coordinate(bounds.getMinLat(), bounds.getMaxLon());
400                            MapRectangle rectangle = new MapRectangleImpl(topLeft, bottomRight);
401                            mapRectangles.put(i, rectangle);
402                            defaultMap.addMapRectangle(rectangle);
403                        }
404                    } else if (mapRectangles.containsKey(i)) {
405                        // Remove previously drawn map rectangle
406                        defaultMap.removeMapRectangle(mapRectangles.get(i));
407                        mapRectangles.remove(i);
408                    }
409                }
410            }
411        }
412
413        private <T> void doCleanupResidualBounds(Map<Integer, T> map, Consumer<T> removalEffect) {
414            for (Integer i : map.keySet()) {
415                int viewIndex = defaultTable.convertRowIndexToView(i);
416                if (!defaultTable.getSelectionModel().isSelectedIndex(viewIndex)) {
417                    removalEffect.accept(map.remove(i));
418                }
419            }
420        }
421
422        private void cleanupResidualBounds() {
423            doCleanupResidualBounds(mapPolygons, l -> l.forEach(defaultMap::removeMapPolygon));
424            doCleanupResidualBounds(mapRectangles, defaultMap::removeMapRectangle);
425        }
426    }
427
428    private class NewEntryAction extends AbstractAction {
429
430        private final ImageryInfo.ImageryType type;
431
432        NewEntryAction(ImageryInfo.ImageryType type) {
433            putValue(NAME, type.toString());
434            putValue(SHORT_DESCRIPTION, tr("Add a new {0} entry by entering the URL", type.toString()));
435            String icon = /* ICON(dialogs/) */ "add";
436            switch (type) {
437            case WMS:
438                icon = /* ICON(dialogs/) */ "add_wms";
439                break;
440            case TMS:
441                icon = /* ICON(dialogs/) */ "add_tms";
442                break;
443            case WMTS:
444                icon = /* ICON(dialogs/) */ "add_wmts";
445                break;
446            default:
447                break;
448            }
449            new ImageProvider("dialogs", icon).getResource().attachImageIcon(this, true);
450            this.type = type;
451        }
452
453        @Override
454        public void actionPerformed(ActionEvent evt) {
455            final AddImageryPanel p;
456            switch (type) {
457            case WMS:
458                p = new AddWMSLayerPanel();
459                break;
460            case TMS:
461                p = new AddTMSLayerPanel();
462                break;
463            case WMTS:
464                p = new AddWMTSLayerPanel();
465                break;
466            default:
467                throw new IllegalStateException("Type " + type + " not supported");
468            }
469
470            final AddImageryDialog addDialog = new AddImageryDialog(gui, p);
471            addDialog.showDialog();
472
473            if (addDialog.getValue() == 1) {
474                try {
475                    activeModel.addRow(p.getImageryInfo());
476                } catch (IllegalArgumentException ex) {
477                    if (ex.getMessage() == null || ex.getMessage().isEmpty())
478                        throw ex;
479                    else {
480                        JOptionPane.showMessageDialog(MainApplication.getMainFrame(),
481                                ex.getMessage(), tr("Error"),
482                                JOptionPane.ERROR_MESSAGE);
483                    }
484                }
485            }
486        }
487    }
488
489    private class RemoveEntryAction extends AbstractAction implements ListSelectionListener {
490
491        /**
492         * Constructs a new {@code RemoveEntryAction}.
493         */
494        RemoveEntryAction() {
495            putValue(NAME, tr("Remove"));
496            putValue(SHORT_DESCRIPTION, tr("Remove entry"));
497            new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this, true);
498            updateEnabledState();
499        }
500
501        protected final void updateEnabledState() {
502            setEnabled(activeTable.getSelectedRowCount() > 0);
503        }
504
505        @Override
506        public void valueChanged(ListSelectionEvent e) {
507            updateEnabledState();
508        }
509
510        @Override
511        public void actionPerformed(ActionEvent e) {
512            Integer i;
513            while ((i = activeTable.getSelectedRow()) != -1) {
514                activeModel.removeRow(i);
515            }
516        }
517    }
518
519    private class ActivateAction extends AbstractAction implements ListSelectionListener {
520
521        /**
522         * Constructs a new {@code ActivateAction}.
523         */
524        ActivateAction() {
525            putValue(NAME, tr("Activate"));
526            putValue(SHORT_DESCRIPTION, tr("Copy selected default entries from the list above into the list below."));
527            new ImageProvider("preferences", "activate-down").getResource().attachImageIcon(this, true);
528        }
529
530        protected void updateEnabledState() {
531            setEnabled(defaultTable.getSelectedRowCount() > 0);
532        }
533
534        @Override
535        public void valueChanged(ListSelectionEvent e) {
536            updateEnabledState();
537        }
538
539        @Override
540        public void actionPerformed(ActionEvent e) {
541            int[] lines = defaultTable.getSelectedRows();
542            if (lines.length == 0) {
543                JOptionPane.showMessageDialog(
544                        gui,
545                        tr("Please select at least one row to copy."),
546                        tr("Information"),
547                        JOptionPane.INFORMATION_MESSAGE);
548                return;
549            }
550
551            Set<String> acceptedEulas = new HashSet<>();
552
553            outer:
554            for (int line : lines) {
555                ImageryInfo info = defaultModel.getRow(defaultTable.convertRowIndexToModel(line));
556
557                // Check if an entry with exactly the same values already exists
558                for (int j = 0; j < activeModel.getRowCount(); j++) {
559                    if (info.equalsBaseValues(activeModel.getRow(j))) {
560                        // Select the already existing row so the user has
561                        // some feedback in case an entry exists
562                        activeTable.getSelectionModel().setSelectionInterval(j, j);
563                        activeTable.scrollRectToVisible(activeTable.getCellRect(j, 0, true));
564                        continue outer;
565                    }
566                }
567
568                String eulaURL = info.getEulaAcceptanceRequired();
569                // If set and not already accepted, ask for EULA acceptance
570                if (eulaURL != null && !acceptedEulas.contains(eulaURL)) {
571                    if (confirmEulaAcceptance(gui, eulaURL)) {
572                        acceptedEulas.add(eulaURL);
573                    } else {
574                        continue outer;
575                    }
576                }
577
578                activeModel.addRow(new ImageryInfo(info));
579                int lastLine = activeModel.getRowCount() - 1;
580                activeTable.getSelectionModel().setSelectionInterval(lastLine, lastLine);
581                activeTable.scrollRectToVisible(activeTable.getCellRect(lastLine, 0, true));
582            }
583        }
584    }
585
586    private class ReloadAction extends AbstractAction {
587
588        /**
589         * Constructs a new {@code ReloadAction}.
590         */
591        ReloadAction() {
592            putValue(SHORT_DESCRIPTION, tr("Update default entries"));
593            new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this, true);
594        }
595
596        @Override
597        public void actionPerformed(ActionEvent evt) {
598            layerInfo.loadDefaults(true, MainApplication.worker, false);
599            defaultModel.fireTableDataChanged();
600            defaultTable.getSelectionModel().clearSelection();
601            defaultTableListener.clearMap();
602            /* loading new file may change active layers */
603            activeModel.fireTableDataChanged();
604        }
605    }
606
607    /**
608     * The table model for imagery layer list
609     */
610    public class ImageryLayerTableModel extends DefaultTableModel {
611        /**
612         * Constructs a new {@code ImageryLayerTableModel}.
613         */
614        public ImageryLayerTableModel() {
615            setColumnIdentifiers(new String[] {tr("Menu Name"), tr("Imagery URL")});
616        }
617
618        /**
619         * Returns the imagery info at the given row number.
620         * @param row The row number
621         * @return The imagery info at the given row number
622         */
623        public ImageryInfo getRow(int row) {
624            return layerInfo.getLayers().get(row);
625        }
626
627        /**
628         * Adds a new imagery info as the last row.
629         * @param i The imagery info to add
630         */
631        public void addRow(ImageryInfo i) {
632            layerInfo.add(i);
633            int p = getRowCount() - 1;
634            fireTableRowsInserted(p, p);
635        }
636
637        @Override
638        public void removeRow(int i) {
639            layerInfo.remove(getRow(i));
640            fireTableRowsDeleted(i, i);
641        }
642
643        @Override
644        public int getRowCount() {
645            return layerInfo.getLayers().size();
646        }
647
648        @Override
649        public Object getValueAt(int row, int column) {
650            ImageryInfo info = layerInfo.getLayers().get(row);
651            switch (column) {
652            case 0:
653                return info.getName();
654            case 1:
655                return info.getExtendedUrl();
656            default:
657                throw new ArrayIndexOutOfBoundsException(Integer.toString(column));
658            }
659        }
660
661        @Override
662        public void setValueAt(Object o, int row, int column) {
663            if (layerInfo.getLayers().size() <= row) return;
664            ImageryInfo info = layerInfo.getLayers().get(row);
665            switch (column) {
666            case 0:
667                info.setName((String) o);
668                info.clearId();
669                break;
670            case 1:
671                info.setExtendedUrl((String) o);
672                info.clearId();
673                break;
674            default:
675                throw new ArrayIndexOutOfBoundsException(Integer.toString(column));
676            }
677        }
678    }
679
680    /**
681     * The table model for the default imagery layer list
682     */
683    public class ImageryDefaultLayerTableModel extends DefaultTableModel {
684        /**
685         * Constructs a new {@code ImageryDefaultLayerTableModel}.
686         */
687        public ImageryDefaultLayerTableModel() {
688            setColumnIdentifiers(new String[]{"", "", tr("Menu Name (Default)"), tr("Imagery URL (Default)")});
689        }
690
691        /**
692         * Returns the imagery info at the given row number.
693         * @param row The row number
694         * @return The imagery info at the given row number
695         */
696        public ImageryInfo getRow(int row) {
697            return layerInfo.getAllDefaultLayers().get(row);
698        }
699
700        @Override
701        public int getRowCount() {
702            return layerInfo.getAllDefaultLayers().size();
703        }
704
705        @Override
706        public Class<?> getColumnClass(int columnIndex) {
707            switch (columnIndex) {
708            case 0:
709                return ImageryCategory.class;
710            case 1:
711                return String.class;
712            case 2:
713                return ImageryInfo.class;
714            case 3:
715                return String.class;
716            default:
717                return super.getColumnClass(columnIndex);
718            }
719        }
720
721        @Override
722        public Object getValueAt(int row, int column) {
723            ImageryInfo info = layerInfo.getAllDefaultLayers().get(row);
724            switch (column) {
725            case 0:
726                return Optional.ofNullable(info.getImageryCategory()).orElse(ImageryCategory.OTHER);
727            case 1:
728                return info.getCountryCode();
729            case 2:
730                return info;
731            case 3:
732                return info.getExtendedUrl();
733            default:
734                return null;
735            }
736        }
737
738        @Override
739        public boolean isCellEditable(int row, int column) {
740            return false;
741        }
742    }
743
744    private static boolean confirmEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) {
745        URL url;
746        try {
747            url = new URL(eulaUrl.replaceAll("\\{lang\\}", LanguageInfo.getWikiLanguagePrefix()));
748            JosmEditorPane htmlPane;
749            try {
750                htmlPane = new JosmEditorPane(url);
751            } catch (IOException e1) {
752                Logging.trace(e1);
753                // give a second chance with a default Locale 'en'
754                try {
755                    url = new URL(eulaUrl.replaceAll("\\{lang\\}", ""));
756                    htmlPane = new JosmEditorPane(url);
757                } catch (IOException e2) {
758                    Logging.debug(e2);
759                    JOptionPane.showMessageDialog(gui, tr("EULA license URL not available: {0}", eulaUrl));
760                    return false;
761                }
762            }
763            Box box = Box.createVerticalBox();
764            htmlPane.setEditable(false);
765            JScrollPane scrollPane = new JScrollPane(htmlPane);
766            scrollPane.setPreferredSize(new Dimension(400, 400));
767            box.add(scrollPane);
768            int option = JOptionPane.showConfirmDialog(MainApplication.getMainFrame(), box, tr("Please abort if you are not sure"),
769                    JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
770            if (option == JOptionPane.YES_OPTION)
771                return true;
772        } catch (MalformedURLException e2) {
773            JOptionPane.showMessageDialog(gui, tr("Malformed URL for the EULA licence: {0}", eulaUrl));
774        }
775        return false;
776    }
777}