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