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