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.Locale; 023import java.util.Map; 024import java.util.Optional; 025import java.util.Set; 026import java.util.function.BiConsumer; 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, code -> code.isEmpty() ? tr("Worldwide") : new Locale("en", code).getDisplayCountry(), 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 updateBoundsAndShapes(defaultTable.convertRowIndexToModel(i)); 352 } 353 // If needed, adjust map to show all map rectangles and polygons 354 if (!mapRectangles.isEmpty() || !mapPolygons.isEmpty()) { 355 defaultMap.setDisplayToFitMapElements(false, true, true); 356 defaultMap.zoomOut(); 357 } 358 } 359 } 360 361 /** 362 * update bounds and shapes for a new entry 363 * @param i model index 364 */ 365 private void updateBoundsAndShapes(int i) { 366 ImageryBounds bounds = defaultModel.getRow(i).getBounds(); 367 if (bounds != null) { 368 int viewIndex = defaultTable.convertRowIndexToView(i); 369 List<Shape> shapes = bounds.getShapes(); 370 if (shapes != null && !shapes.isEmpty()) { 371 if (defaultTable.getSelectionModel().isSelectedIndex(viewIndex)) { 372 if (!mapPolygons.containsKey(i)) { 373 List<MapPolygon> list = new ArrayList<>(); 374 mapPolygons.put(i, list); 375 // Add new map polygons 376 for (Shape shape : shapes) { 377 MapPolygon polygon = new MapPolygonImpl(shape.getPoints()); 378 list.add(polygon); 379 defaultMap.addMapPolygon(polygon); 380 } 381 } 382 } else if (mapPolygons.containsKey(i)) { 383 // Remove previously drawn map polygons 384 for (MapPolygon polygon : mapPolygons.get(i)) { 385 defaultMap.removeMapPolygon(polygon); 386 } 387 mapPolygons.remove(i); 388 } 389 // Only display bounds when no polygons (shapes) are defined for this provider 390 } else { 391 if (defaultTable.getSelectionModel().isSelectedIndex(viewIndex)) { 392 if (!mapRectangles.containsKey(i)) { 393 // Add new map rectangle 394 Coordinate topLeft = new Coordinate(bounds.getMaxLat(), bounds.getMinLon()); 395 Coordinate bottomRight = new Coordinate(bounds.getMinLat(), bounds.getMaxLon()); 396 MapRectangle rectangle = new MapRectangleImpl(topLeft, bottomRight); 397 mapRectangles.put(i, rectangle); 398 defaultMap.addMapRectangle(rectangle); 399 } 400 } else if (mapRectangles.containsKey(i)) { 401 // Remove previously drawn map rectangle 402 defaultMap.removeMapRectangle(mapRectangles.get(i)); 403 mapRectangles.remove(i); 404 } 405 } 406 } 407 } 408 } 409 410 private class NewEntryAction extends AbstractAction { 411 412 private final ImageryInfo.ImageryType type; 413 414 NewEntryAction(ImageryInfo.ImageryType type) { 415 putValue(NAME, type.toString()); 416 putValue(SHORT_DESCRIPTION, tr("Add a new {0} entry by entering the URL", type.toString())); 417 String icon = /* ICON(dialogs/) */ "add"; 418 switch (type) { 419 case WMS: 420 icon = /* ICON(dialogs/) */ "add_wms"; 421 break; 422 case TMS: 423 icon = /* ICON(dialogs/) */ "add_tms"; 424 break; 425 case WMTS: 426 icon = /* ICON(dialogs/) */ "add_wmts"; 427 break; 428 default: 429 break; 430 } 431 new ImageProvider("dialogs", icon).getResource().attachImageIcon(this, true); 432 this.type = type; 433 } 434 435 @Override 436 public void actionPerformed(ActionEvent evt) { 437 final AddImageryPanel p; 438 switch (type) { 439 case WMS: 440 p = new AddWMSLayerPanel(); 441 break; 442 case TMS: 443 p = new AddTMSLayerPanel(); 444 break; 445 case WMTS: 446 p = new AddWMTSLayerPanel(); 447 break; 448 default: 449 throw new IllegalStateException("Type " + type + " not supported"); 450 } 451 452 final AddImageryDialog addDialog = new AddImageryDialog(gui, p); 453 addDialog.showDialog(); 454 455 if (addDialog.getValue() == 1) { 456 try { 457 activeModel.addRow(p.getImageryInfo()); 458 } catch (IllegalArgumentException ex) { 459 if (ex.getMessage() == null || ex.getMessage().isEmpty()) 460 throw ex; 461 else { 462 JOptionPane.showMessageDialog(MainApplication.getMainFrame(), 463 ex.getMessage(), tr("Error"), 464 JOptionPane.ERROR_MESSAGE); 465 } 466 } 467 } 468 } 469 } 470 471 private class RemoveEntryAction extends AbstractAction implements ListSelectionListener { 472 473 /** 474 * Constructs a new {@code RemoveEntryAction}. 475 */ 476 RemoveEntryAction() { 477 putValue(NAME, tr("Remove")); 478 putValue(SHORT_DESCRIPTION, tr("Remove entry")); 479 new ImageProvider("dialogs", "delete").getResource().attachImageIcon(this, true); 480 updateEnabledState(); 481 } 482 483 protected final void updateEnabledState() { 484 setEnabled(activeTable.getSelectedRowCount() > 0); 485 } 486 487 @Override 488 public void valueChanged(ListSelectionEvent e) { 489 updateEnabledState(); 490 } 491 492 @Override 493 public void actionPerformed(ActionEvent e) { 494 Integer i; 495 while ((i = activeTable.getSelectedRow()) != -1) { 496 activeModel.removeRow(i); 497 } 498 } 499 } 500 501 private class ActivateAction extends AbstractAction implements ListSelectionListener { 502 503 /** 504 * Constructs a new {@code ActivateAction}. 505 */ 506 ActivateAction() { 507 putValue(NAME, tr("Activate")); 508 putValue(SHORT_DESCRIPTION, tr("Copy selected default entries from the list above into the list below.")); 509 new ImageProvider("preferences", "activate-down").getResource().attachImageIcon(this, true); 510 } 511 512 protected void updateEnabledState() { 513 setEnabled(defaultTable.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 int[] lines = defaultTable.getSelectedRows(); 524 if (lines.length == 0) { 525 JOptionPane.showMessageDialog( 526 gui, 527 tr("Please select at least one row to copy."), 528 tr("Information"), 529 JOptionPane.INFORMATION_MESSAGE); 530 return; 531 } 532 533 Set<String> acceptedEulas = new HashSet<>(); 534 535 outer: 536 for (int line : lines) { 537 ImageryInfo info = defaultModel.getRow(defaultTable.convertRowIndexToModel(line)); 538 539 // Check if an entry with exactly the same values already exists 540 for (int j = 0; j < activeModel.getRowCount(); j++) { 541 if (info.equalsBaseValues(activeModel.getRow(j))) { 542 // Select the already existing row so the user has 543 // some feedback in case an entry exists 544 activeTable.getSelectionModel().setSelectionInterval(j, j); 545 activeTable.scrollRectToVisible(activeTable.getCellRect(j, 0, true)); 546 continue outer; 547 } 548 } 549 550 String eulaURL = info.getEulaAcceptanceRequired(); 551 // If set and not already accepted, ask for EULA acceptance 552 if (eulaURL != null && !acceptedEulas.contains(eulaURL)) { 553 if (confirmEulaAcceptance(gui, eulaURL)) { 554 acceptedEulas.add(eulaURL); 555 } else { 556 continue outer; 557 } 558 } 559 560 activeModel.addRow(new ImageryInfo(info)); 561 int lastLine = activeModel.getRowCount() - 1; 562 activeTable.getSelectionModel().setSelectionInterval(lastLine, lastLine); 563 activeTable.scrollRectToVisible(activeTable.getCellRect(lastLine, 0, true)); 564 } 565 } 566 } 567 568 private class ReloadAction extends AbstractAction { 569 570 /** 571 * Constructs a new {@code ReloadAction}. 572 */ 573 ReloadAction() { 574 putValue(SHORT_DESCRIPTION, tr("Update default entries")); 575 new ImageProvider("dialogs", "refresh").getResource().attachImageIcon(this, true); 576 } 577 578 @Override 579 public void actionPerformed(ActionEvent evt) { 580 layerInfo.loadDefaults(true, MainApplication.worker, false); 581 defaultModel.fireTableDataChanged(); 582 defaultTable.getSelectionModel().clearSelection(); 583 defaultTableListener.clearMap(); 584 /* loading new file may change active layers */ 585 activeModel.fireTableDataChanged(); 586 } 587 } 588 589 /** 590 * The table model for imagery layer list 591 */ 592 public class ImageryLayerTableModel extends DefaultTableModel { 593 /** 594 * Constructs a new {@code ImageryLayerTableModel}. 595 */ 596 public ImageryLayerTableModel() { 597 setColumnIdentifiers(new String[] {tr("Menu Name"), tr("Imagery URL")}); 598 } 599 600 /** 601 * Returns the imagery info at the given row number. 602 * @param row The row number 603 * @return The imagery info at the given row number 604 */ 605 public ImageryInfo getRow(int row) { 606 return layerInfo.getLayers().get(row); 607 } 608 609 /** 610 * Adds a new imagery info as the last row. 611 * @param i The imagery info to add 612 */ 613 public void addRow(ImageryInfo i) { 614 layerInfo.add(i); 615 int p = getRowCount() - 1; 616 fireTableRowsInserted(p, p); 617 } 618 619 @Override 620 public void removeRow(int i) { 621 layerInfo.remove(getRow(i)); 622 fireTableRowsDeleted(i, i); 623 } 624 625 @Override 626 public int getRowCount() { 627 return layerInfo.getLayers().size(); 628 } 629 630 @Override 631 public Object getValueAt(int row, int column) { 632 ImageryInfo info = layerInfo.getLayers().get(row); 633 switch (column) { 634 case 0: 635 return info.getName(); 636 case 1: 637 return info.getExtendedUrl(); 638 default: 639 throw new ArrayIndexOutOfBoundsException(Integer.toString(column)); 640 } 641 } 642 643 @Override 644 public void setValueAt(Object o, int row, int column) { 645 if (layerInfo.getLayers().size() <= row) return; 646 ImageryInfo info = layerInfo.getLayers().get(row); 647 switch (column) { 648 case 0: 649 info.setName((String) o); 650 info.clearId(); 651 break; 652 case 1: 653 info.setExtendedUrl((String) o); 654 info.clearId(); 655 break; 656 default: 657 throw new ArrayIndexOutOfBoundsException(Integer.toString(column)); 658 } 659 } 660 } 661 662 /** 663 * The table model for the default imagery layer list 664 */ 665 public class ImageryDefaultLayerTableModel extends DefaultTableModel { 666 /** 667 * Constructs a new {@code ImageryDefaultLayerTableModel}. 668 */ 669 public ImageryDefaultLayerTableModel() { 670 setColumnIdentifiers(new String[]{"", "", tr("Menu Name (Default)"), tr("Imagery URL (Default)")}); 671 } 672 673 /** 674 * Returns the imagery info at the given row number. 675 * @param row The row number 676 * @return The imagery info at the given row number 677 */ 678 public ImageryInfo getRow(int row) { 679 return layerInfo.getAllDefaultLayers().get(row); 680 } 681 682 @Override 683 public int getRowCount() { 684 return layerInfo.getAllDefaultLayers().size(); 685 } 686 687 @Override 688 public Object getValueAt(int row, int column) { 689 ImageryInfo info = layerInfo.getAllDefaultLayers().get(row); 690 switch (column) { 691 case 0: 692 return Optional.ofNullable(info.getImageryCategory()).orElse(ImageryCategory.OTHER); 693 case 1: 694 return info.getCountryCode(); 695 case 2: 696 return info; 697 case 3: 698 return info.getExtendedUrl(); 699 default: 700 return null; 701 } 702 } 703 704 @Override 705 public boolean isCellEditable(int row, int column) { 706 return false; 707 } 708 } 709 710 private static boolean confirmEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) { 711 URL url; 712 try { 713 url = new URL(eulaUrl.replaceAll("\\{lang\\}", LanguageInfo.getWikiLanguagePrefix())); 714 JosmEditorPane htmlPane; 715 try { 716 htmlPane = new JosmEditorPane(url); 717 } catch (IOException e1) { 718 Logging.trace(e1); 719 // give a second chance with a default Locale 'en' 720 try { 721 url = new URL(eulaUrl.replaceAll("\\{lang\\}", "")); 722 htmlPane = new JosmEditorPane(url); 723 } catch (IOException e2) { 724 Logging.debug(e2); 725 JOptionPane.showMessageDialog(gui, tr("EULA license URL not available: {0}", eulaUrl)); 726 return false; 727 } 728 } 729 Box box = Box.createVerticalBox(); 730 htmlPane.setEditable(false); 731 JScrollPane scrollPane = new JScrollPane(htmlPane); 732 scrollPane.setPreferredSize(new Dimension(400, 400)); 733 box.add(scrollPane); 734 int option = JOptionPane.showConfirmDialog(MainApplication.getMainFrame(), box, tr("Please abort if you are not sure"), 735 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); 736 if (option == JOptionPane.YES_OPTION) 737 return true; 738 } catch (MalformedURLException e2) { 739 JOptionPane.showMessageDialog(gui, tr("Malformed URL for the EULA licence: {0}", eulaUrl)); 740 } 741 return false; 742 } 743}