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}