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.FlowLayout; 011import java.awt.Font; 012import java.awt.GraphicsEnvironment; 013import java.awt.GridBagConstraints; 014import java.awt.GridBagLayout; 015import java.awt.event.ActionEvent; 016import java.awt.event.MouseAdapter; 017import java.awt.event.MouseEvent; 018import java.io.IOException; 019import java.net.MalformedURLException; 020import java.net.URL; 021import java.util.ArrayList; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027 028import javax.swing.AbstractAction; 029import javax.swing.BorderFactory; 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.JSeparator; 037import javax.swing.JTabbedPane; 038import javax.swing.JTable; 039import javax.swing.JToolBar; 040import javax.swing.UIManager; 041import javax.swing.event.ListSelectionEvent; 042import javax.swing.event.ListSelectionListener; 043import javax.swing.table.DefaultTableCellRenderer; 044import javax.swing.table.DefaultTableModel; 045import javax.swing.table.TableColumnModel; 046 047import org.openstreetmap.gui.jmapviewer.Coordinate; 048import org.openstreetmap.gui.jmapviewer.JMapViewer; 049import org.openstreetmap.gui.jmapviewer.MapPolygonImpl; 050import org.openstreetmap.gui.jmapviewer.MapRectangleImpl; 051import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon; 052import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle; 053import org.openstreetmap.gui.jmapviewer.tilesources.OsmTileSource; 054import org.openstreetmap.josm.Main; 055import org.openstreetmap.josm.data.imagery.ImageryInfo; 056import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds; 057import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; 058import org.openstreetmap.josm.data.imagery.OffsetBookmark; 059import org.openstreetmap.josm.data.imagery.Shape; 060import org.openstreetmap.josm.data.preferences.ColorProperty; 061import org.openstreetmap.josm.gui.download.DownloadDialog; 062import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting; 063import org.openstreetmap.josm.gui.preferences.PreferenceSetting; 064import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory; 065import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane; 066import org.openstreetmap.josm.gui.util.GuiHelper; 067import org.openstreetmap.josm.gui.widgets.JosmEditorPane; 068import org.openstreetmap.josm.tools.GBC; 069import org.openstreetmap.josm.tools.ImageProvider; 070import org.openstreetmap.josm.tools.LanguageInfo; 071 072/** 073 * Imagery preferences, including imagery providers, settings and offsets. 074 * @since 3715 075 */ 076public final class ImageryPreference extends DefaultTabPreferenceSetting { 077 078 private ImageryProvidersPanel imageryProviders; 079 private ImageryLayerInfo layerInfo; 080 081 private final CommonSettingsPanel commonSettings = new CommonSettingsPanel(); 082 private final WMSSettingsPanel wmsSettings = new WMSSettingsPanel(); 083 private final TMSSettingsPanel tmsSettings = new TMSSettingsPanel(); 084 085 /** 086 * Factory used to create a new {@code ImageryPreference}. 087 */ 088 public static class Factory implements PreferenceSettingFactory { 089 @Override 090 public PreferenceSetting createPreferenceSetting() { 091 return new ImageryPreference(); 092 } 093 } 094 095 private ImageryPreference() { 096 super(/* ICON(preferences/) */ "imagery", tr("Imagery Preferences"), tr("Modify list of imagery layers displayed in the Imagery menu"), 097 false, new JTabbedPane()); 098 } 099 100 private static void addSettingsSection(final JPanel p, String name, JPanel section) { 101 addSettingsSection(p, name, section, GBC.eol()); 102 } 103 104 private static void addSettingsSection(final JPanel p, String name, JPanel section, GBC gbc) { 105 final JLabel lbl = new JLabel(name); 106 lbl.setFont(lbl.getFont().deriveFont(Font.BOLD)); 107 lbl.setLabelFor(section); 108 p.add(lbl, GBC.std()); 109 p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0)); 110 p.add(section, gbc.insets(20, 5, 0, 10)); 111 } 112 113 private Component buildSettingsPanel() { 114 final JPanel p = new JPanel(new GridBagLayout()); 115 p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); 116 117 addSettingsSection(p, tr("Common Settings"), commonSettings); 118 addSettingsSection(p, tr("WMS Settings"), wmsSettings, 119 GBC.eol().fill(GBC.HORIZONTAL)); 120 addSettingsSection(p, tr("TMS Settings"), tmsSettings, 121 GBC.eol().fill(GBC.HORIZONTAL)); 122 123 p.add(new JPanel(), GBC.eol().fill(GBC.BOTH)); 124 return GuiHelper.setDefaultIncrement(new JScrollPane(p)); 125 } 126 127 @Override 128 public void addGui(final PreferenceTabbedPane gui) { 129 JPanel p = gui.createPreferenceTab(this); 130 JTabbedPane pane = getTabPane(); 131 layerInfo = new ImageryLayerInfo(ImageryLayerInfo.instance); 132 imageryProviders = new ImageryProvidersPanel(gui, layerInfo); 133 pane.addTab(tr("Imagery providers"), imageryProviders); 134 pane.addTab(tr("Settings"), buildSettingsPanel()); 135 pane.addTab(tr("Offset bookmarks"), new OffsetBookmarksPanel(gui)); 136 pane.addTab(tr("Cache contents"), new CacheContentsPanel()); 137 loadSettings(); 138 p.add(pane, GBC.std().fill(GBC.BOTH)); 139 } 140 141 /** 142 * Returns the imagery providers panel. 143 * @return The imagery providers panel. 144 */ 145 public ImageryProvidersPanel getProvidersPanel() { 146 return imageryProviders; 147 } 148 149 private void loadSettings() { 150 commonSettings.loadSettings(); 151 wmsSettings.loadSettings(); 152 tmsSettings.loadSettings(); 153 } 154 155 @Override 156 public boolean ok() { 157 layerInfo.save(); 158 ImageryLayerInfo.instance.clear(); 159 ImageryLayerInfo.instance.load(false); 160 Main.main.menu.imageryMenu.refreshOffsetMenu(); 161 OffsetBookmark.saveBookmarks(); 162 163 if (!GraphicsEnvironment.isHeadless()) { 164 DownloadDialog.getInstance().refreshTileSources(); 165 } 166 167 boolean commonRestartRequired = commonSettings.saveSettings(); 168 boolean wmsRestartRequired = wmsSettings.saveSettings(); 169 boolean tmsRestartRequired = tmsSettings.saveSettings(); 170 171 return commonRestartRequired || wmsRestartRequired || tmsRestartRequired; 172 } 173 174 /** 175 * Updates a server URL in the preferences dialog. Used by plugins. 176 * 177 * @param server 178 * The server name 179 * @param url 180 * The server URL 181 */ 182 public void setServerUrl(String server, String url) { 183 for (int i = 0; i < imageryProviders.activeModel.getRowCount(); i++) { 184 if (server.equals(imageryProviders.activeModel.getValueAt(i, 0).toString())) { 185 imageryProviders.activeModel.setValueAt(url, i, 1); 186 return; 187 } 188 } 189 imageryProviders.activeModel.addRow(new String[] {server, url}); 190 } 191 192 /** 193 * Gets a server URL in the preferences dialog. Used by plugins. 194 * 195 * @param server The server name 196 * @return The server URL 197 */ 198 public String getServerUrl(String server) { 199 for (int i = 0; i < imageryProviders.activeModel.getRowCount(); i++) { 200 if (server.equals(imageryProviders.activeModel.getValueAt(i, 0).toString())) 201 return imageryProviders.activeModel.getValueAt(i, 1).toString(); 202 } 203 return null; 204 } 205 206 /** 207 * A panel displaying imagery providers. 208 */ 209 public static class ImageryProvidersPanel extends JPanel { 210 // Public JTables and JMapViewer 211 /** The table of active providers **/ 212 public final JTable activeTable; 213 /** The table of default providers **/ 214 public final JTable defaultTable; 215 /** The selection listener synchronizing map display with table of default providers **/ 216 private final transient DefListSelectionListener defaultTableListener; 217 /** The map displaying imagery bounds of selected default providers **/ 218 public final JMapViewer defaultMap; 219 220 // Public models 221 /** The model of active providers **/ 222 public final ImageryLayerTableModel activeModel; 223 /** The model of default providers **/ 224 public final ImageryDefaultLayerTableModel defaultModel; 225 226 // Public JToolbars 227 /** The toolbar on the right of active providers **/ 228 public final JToolBar activeToolbar; 229 /** The toolbar on the middle of the panel **/ 230 public final JToolBar middleToolbar; 231 /** The toolbar on the right of default providers **/ 232 public final JToolBar defaultToolbar; 233 234 // Private members 235 private final PreferenceTabbedPane gui; 236 private final transient ImageryLayerInfo layerInfo; 237 238 /** 239 * class to render the URL information of Imagery source 240 * @since 8065 241 */ 242 private static class ImageryURLTableCellRenderer extends DefaultTableCellRenderer { 243 244 private static final ColorProperty IMAGERY_BACKGROUND_COLOR = new ColorProperty( 245 marktr("Imagery Background: Default"), 246 new Color(200, 255, 200)); 247 248 private final transient List<ImageryInfo> layers; 249 250 ImageryURLTableCellRenderer(List<ImageryInfo> layers) { 251 this.layers = layers; 252 } 253 254 @Override 255 public Component getTableCellRendererComponent(JTable table, Object value, boolean 256 isSelected, boolean hasFocus, int row, int column) { 257 JLabel label = (JLabel) super.getTableCellRendererComponent( 258 table, value, isSelected, hasFocus, row, column); 259 GuiHelper.setBackgroundReadable(label, UIManager.getColor("Table.background")); 260 if (value != null) { // Fix #8159 261 String t = value.toString(); 262 for (ImageryInfo l : layers) { 263 if (l.getExtendedUrl().equals(t)) { 264 GuiHelper.setBackgroundReadable(label, IMAGERY_BACKGROUND_COLOR.get()); 265 break; 266 } 267 } 268 label.setToolTipText((String) value); 269 } 270 return label; 271 } 272 } 273 274 /** 275 * class to render the name information of Imagery source 276 * @since 8064 277 */ 278 private static class ImageryNameTableCellRenderer extends DefaultTableCellRenderer { 279 @Override 280 public Component getTableCellRendererComponent(JTable table, Object value, boolean 281 isSelected, boolean hasFocus, int row, int column) { 282 ImageryInfo info = (ImageryInfo) value; 283 JLabel label = (JLabel) super.getTableCellRendererComponent( 284 table, info == null ? null : info.getName(), isSelected, hasFocus, row, column); 285 GuiHelper.setBackgroundReadable(label, UIManager.getColor("Table.background")); 286 if (info != null) { 287 label.setToolTipText(info.getToolTipText()); 288 } 289 return label; 290 } 291 } 292 293 /** 294 * Constructs a new {@code ImageryProvidersPanel}. 295 * @param gui The parent preference tab pane 296 * @param layerInfoArg The list of imagery entries to display 297 */ 298 public ImageryProvidersPanel(final PreferenceTabbedPane gui, ImageryLayerInfo layerInfoArg) { 299 super(new GridBagLayout()); 300 this.gui = gui; 301 this.layerInfo = layerInfoArg; 302 this.activeModel = new ImageryLayerTableModel(); 303 304 activeTable = new JTable(activeModel) { 305 @Override 306 public String getToolTipText(MouseEvent e) { 307 java.awt.Point p = e.getPoint(); 308 return activeModel.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString(); 309 } 310 }; 311 activeTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 312 313 defaultModel = new ImageryDefaultLayerTableModel(); 314 defaultTable = new JTable(defaultModel); 315 316 defaultModel.addTableModelListener(e -> activeTable.repaint()); 317 activeModel.addTableModelListener(e -> defaultTable.repaint()); 318 319 TableColumnModel mod = defaultTable.getColumnModel(); 320 mod.getColumn(2).setPreferredWidth(800); 321 mod.getColumn(2).setCellRenderer(new ImageryURLTableCellRenderer(layerInfo.getLayers())); 322 mod.getColumn(1).setPreferredWidth(400); 323 mod.getColumn(1).setCellRenderer(new ImageryNameTableCellRenderer()); 324 mod.getColumn(0).setPreferredWidth(50); 325 326 mod = activeTable.getColumnModel(); 327 mod.getColumn(1).setPreferredWidth(800); 328 mod.getColumn(1).setCellRenderer(new ImageryURLTableCellRenderer(layerInfo.getDefaultLayers())); 329 mod.getColumn(0).setPreferredWidth(200); 330 331 RemoveEntryAction remove = new RemoveEntryAction(); 332 activeTable.getSelectionModel().addListSelectionListener(remove); 333 334 add(new JLabel(tr("Available default entries:")), GBC.eol().insets(5, 5, 0, 0)); 335 // Add default item list 336 JScrollPane scrolldef = new JScrollPane(defaultTable); 337 scrolldef.setPreferredSize(new Dimension(200, 200)); 338 add(scrolldef, GBC.std().insets(0, 5, 0, 0).fill(GridBagConstraints.BOTH).weight(1.0, 0.6).insets(5, 0, 0, 0)); 339 340 // Add default item map 341 defaultMap = new JMapViewer(); 342 defaultMap.setTileSource(new OsmTileSource.Mapnik()); // for attribution 343 defaultMap.addMouseListener(new MouseAdapter() { 344 @Override 345 public void mouseClicked(MouseEvent e) { 346 if (e.getButton() == MouseEvent.BUTTON1) { 347 defaultMap.getAttribution().handleAttribution(e.getPoint(), true); 348 } 349 } 350 }); 351 defaultMap.setZoomContolsVisible(false); 352 defaultMap.setMinimumSize(new Dimension(100, 200)); 353 add(defaultMap, GBC.std().insets(5, 5, 0, 0).fill(GridBagConstraints.BOTH).weight(0.33, 0.6).insets(5, 0, 0, 0)); 354 355 defaultTableListener = new DefListSelectionListener(); 356 defaultTable.getSelectionModel().addListSelectionListener(defaultTableListener); 357 358 defaultToolbar = new JToolBar(JToolBar.VERTICAL); 359 defaultToolbar.setFloatable(false); 360 defaultToolbar.setBorderPainted(false); 361 defaultToolbar.setOpaque(false); 362 defaultToolbar.add(new ReloadAction()); 363 add(defaultToolbar, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 5, 0)); 364 365 ActivateAction activate = new ActivateAction(); 366 defaultTable.getSelectionModel().addListSelectionListener(activate); 367 JButton btnActivate = new JButton(activate); 368 369 middleToolbar = new JToolBar(JToolBar.HORIZONTAL); 370 middleToolbar.setFloatable(false); 371 middleToolbar.setBorderPainted(false); 372 middleToolbar.setOpaque(false); 373 middleToolbar.add(btnActivate); 374 add(middleToolbar, GBC.eol().anchor(GBC.CENTER).insets(5, 15, 5, 0)); 375 376 add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 377 378 add(new JLabel(tr("Selected entries:")), GBC.eol().insets(5, 0, 0, 0)); 379 JScrollPane scroll = new JScrollPane(activeTable); 380 add(scroll, GBC.std().fill(GridBagConstraints.BOTH).span(GridBagConstraints.RELATIVE).weight(1.0, 0.4).insets(5, 0, 0, 5)); 381 scroll.setPreferredSize(new Dimension(200, 200)); 382 383 activeToolbar = new JToolBar(JToolBar.VERTICAL); 384 activeToolbar.setFloatable(false); 385 activeToolbar.setBorderPainted(false); 386 activeToolbar.setOpaque(false); 387 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMS)); 388 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.TMS)); 389 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMTS)); 390 //activeToolbar.add(edit); TODO 391 activeToolbar.add(remove); 392 add(activeToolbar, GBC.eol().anchor(GBC.NORTH).insets(0, 0, 5, 5)); 393 } 394 395 // Listener of default providers list selection 396 private final class DefListSelectionListener implements ListSelectionListener { 397 // The current drawn rectangles and polygons 398 private final Map<Integer, MapRectangle> mapRectangles; 399 private final Map<Integer, List<MapPolygon>> mapPolygons; 400 401 private DefListSelectionListener() { 402 this.mapRectangles = new HashMap<>(); 403 this.mapPolygons = new HashMap<>(); 404 } 405 406 private void clearMap() { 407 defaultMap.removeAllMapRectangles(); 408 defaultMap.removeAllMapPolygons(); 409 mapRectangles.clear(); 410 mapPolygons.clear(); 411 } 412 413 @Override 414 public void valueChanged(ListSelectionEvent e) { 415 // First index can be set to -1 when the list is refreshed, so discard all map rectangles and polygons 416 if (e.getFirstIndex() == -1) { 417 clearMap(); 418 } else if (!e.getValueIsAdjusting()) { 419 // Only process complete (final) selection events 420 for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) { 421 updateBoundsAndShapes(i); 422 } 423 // If needed, adjust map to show all map rectangles and polygons 424 if (!mapRectangles.isEmpty() || !mapPolygons.isEmpty()) { 425 defaultMap.setDisplayToFitMapElements(false, true, true); 426 defaultMap.zoomOut(); 427 } 428 } 429 } 430 431 private void updateBoundsAndShapes(int i) { 432 ImageryBounds bounds = defaultModel.getRow(i).getBounds(); 433 if (bounds != null) { 434 List<Shape> shapes = bounds.getShapes(); 435 if (shapes != null && !shapes.isEmpty()) { 436 if (defaultTable.getSelectionModel().isSelectedIndex(i)) { 437 if (!mapPolygons.containsKey(i)) { 438 List<MapPolygon> list = new ArrayList<>(); 439 mapPolygons.put(i, list); 440 // Add new map polygons 441 for (Shape shape : shapes) { 442 MapPolygon polygon = new MapPolygonImpl(shape.getPoints()); 443 list.add(polygon); 444 defaultMap.addMapPolygon(polygon); 445 } 446 } 447 } else if (mapPolygons.containsKey(i)) { 448 // Remove previously drawn map polygons 449 for (MapPolygon polygon : mapPolygons.get(i)) { 450 defaultMap.removeMapPolygon(polygon); 451 } 452 mapPolygons.remove(i); 453 } 454 // Only display bounds when no polygons (shapes) are defined for this provider 455 } else { 456 if (defaultTable.getSelectionModel().isSelectedIndex(i)) { 457 if (!mapRectangles.containsKey(i)) { 458 // Add new map rectangle 459 Coordinate topLeft = new Coordinate(bounds.getMaxLat(), bounds.getMinLon()); 460 Coordinate bottomRight = new Coordinate(bounds.getMinLat(), bounds.getMaxLon()); 461 MapRectangle rectangle = new MapRectangleImpl(topLeft, bottomRight); 462 mapRectangles.put(i, rectangle); 463 defaultMap.addMapRectangle(rectangle); 464 } 465 } else if (mapRectangles.containsKey(i)) { 466 // Remove previously drawn map rectangle 467 defaultMap.removeMapRectangle(mapRectangles.get(i)); 468 mapRectangles.remove(i); 469 } 470 } 471 } 472 } 473 } 474 475 private class NewEntryAction extends AbstractAction { 476 477 private final ImageryInfo.ImageryType type; 478 479 NewEntryAction(ImageryInfo.ImageryType type) { 480 putValue(NAME, type.toString()); 481 putValue(SHORT_DESCRIPTION, tr("Add a new {0} entry by entering the URL", type.toString())); 482 String icon = /* ICON(dialogs/) */ "add"; 483 switch (type) { 484 case WMS: 485 icon = /* ICON(dialogs/) */ "add_wms"; 486 break; 487 case TMS: 488 icon = /* ICON(dialogs/) */ "add_tms"; 489 break; 490 case WMTS: 491 icon = /* ICON(dialogs/) */ "add_wmts"; 492 break; 493 default: 494 break; 495 } 496 putValue(SMALL_ICON, ImageProvider.get("dialogs", icon)); 497 this.type = type; 498 } 499 500 @Override 501 public void actionPerformed(ActionEvent evt) { 502 final AddImageryPanel p; 503 switch (type) { 504 case WMS: 505 p = new AddWMSLayerPanel(); 506 break; 507 case TMS: 508 p = new AddTMSLayerPanel(); 509 break; 510 case WMTS: 511 p = new AddWMTSLayerPanel(); 512 break; 513 default: 514 throw new IllegalStateException("Type " + type + " not supported"); 515 } 516 517 final AddImageryDialog addDialog = new AddImageryDialog(gui, p); 518 addDialog.showDialog(); 519 520 if (addDialog.getValue() == 1) { 521 try { 522 activeModel.addRow(p.getImageryInfo()); 523 } catch (IllegalArgumentException ex) { 524 if (ex.getMessage() == null || ex.getMessage().isEmpty()) 525 throw ex; 526 else { 527 JOptionPane.showMessageDialog(Main.parent, 528 ex.getMessage(), tr("Error"), 529 JOptionPane.ERROR_MESSAGE); 530 } 531 } 532 } 533 } 534 } 535 536 private class RemoveEntryAction extends AbstractAction implements ListSelectionListener { 537 538 /** 539 * Constructs a new {@code RemoveEntryAction}. 540 */ 541 RemoveEntryAction() { 542 putValue(NAME, tr("Remove")); 543 putValue(SHORT_DESCRIPTION, tr("Remove entry")); 544 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 545 updateEnabledState(); 546 } 547 548 protected final void updateEnabledState() { 549 setEnabled(activeTable.getSelectedRowCount() > 0); 550 } 551 552 @Override 553 public void valueChanged(ListSelectionEvent e) { 554 updateEnabledState(); 555 } 556 557 @Override 558 public void actionPerformed(ActionEvent e) { 559 Integer i; 560 while ((i = activeTable.getSelectedRow()) != -1) { 561 activeModel.removeRow(i); 562 } 563 } 564 } 565 566 private class ActivateAction extends AbstractAction implements ListSelectionListener { 567 568 /** 569 * Constructs a new {@code ActivateAction}. 570 */ 571 ActivateAction() { 572 putValue(NAME, tr("Activate")); 573 putValue(SHORT_DESCRIPTION, tr("copy selected defaults")); 574 putValue(SMALL_ICON, ImageProvider.get("preferences", "activate-down")); 575 } 576 577 protected void updateEnabledState() { 578 setEnabled(defaultTable.getSelectedRowCount() > 0); 579 } 580 581 @Override 582 public void valueChanged(ListSelectionEvent e) { 583 updateEnabledState(); 584 } 585 586 @Override 587 public void actionPerformed(ActionEvent e) { 588 int[] lines = defaultTable.getSelectedRows(); 589 if (lines.length == 0) { 590 JOptionPane.showMessageDialog( 591 gui, 592 tr("Please select at least one row to copy."), 593 tr("Information"), 594 JOptionPane.INFORMATION_MESSAGE); 595 return; 596 } 597 598 Set<String> acceptedEulas = new HashSet<>(); 599 600 outer: 601 for (int line : lines) { 602 ImageryInfo info = defaultModel.getRow(line); 603 604 // Check if an entry with exactly the same values already exists 605 for (int j = 0; j < activeModel.getRowCount(); j++) { 606 if (info.equalsBaseValues(activeModel.getRow(j))) { 607 // Select the already existing row so the user has 608 // some feedback in case an entry exists 609 activeTable.getSelectionModel().setSelectionInterval(j, j); 610 activeTable.scrollRectToVisible(activeTable.getCellRect(j, 0, true)); 611 continue outer; 612 } 613 } 614 615 String eulaURL = info.getEulaAcceptanceRequired(); 616 // If set and not already accepted, ask for EULA acceptance 617 if (eulaURL != null && !acceptedEulas.contains(eulaURL)) { 618 if (confirmEulaAcceptance(gui, eulaURL)) { 619 acceptedEulas.add(eulaURL); 620 } else { 621 continue outer; 622 } 623 } 624 625 activeModel.addRow(new ImageryInfo(info)); 626 int lastLine = activeModel.getRowCount() - 1; 627 activeTable.getSelectionModel().setSelectionInterval(lastLine, lastLine); 628 activeTable.scrollRectToVisible(activeTable.getCellRect(lastLine, 0, true)); 629 } 630 } 631 } 632 633 private class ReloadAction extends AbstractAction { 634 635 /** 636 * Constructs a new {@code ReloadAction}. 637 */ 638 ReloadAction() { 639 putValue(SHORT_DESCRIPTION, tr("Update default entries")); 640 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh")); 641 } 642 643 @Override 644 public void actionPerformed(ActionEvent evt) { 645 layerInfo.loadDefaults(true, false, false); 646 defaultModel.fireTableDataChanged(); 647 defaultTable.getSelectionModel().clearSelection(); 648 defaultTableListener.clearMap(); 649 /* loading new file may change active layers */ 650 activeModel.fireTableDataChanged(); 651 } 652 } 653 654 /** 655 * The table model for imagery layer list 656 */ 657 public class ImageryLayerTableModel extends DefaultTableModel { 658 /** 659 * Constructs a new {@code ImageryLayerTableModel}. 660 */ 661 public ImageryLayerTableModel() { 662 setColumnIdentifiers(new String[] {tr("Menu Name"), tr("Imagery URL")}); 663 } 664 665 /** 666 * Returns the imagery info at the given row number. 667 * @param row The row number 668 * @return The imagery info at the given row number 669 */ 670 public ImageryInfo getRow(int row) { 671 return layerInfo.getLayers().get(row); 672 } 673 674 /** 675 * Adds a new imagery info as the last row. 676 * @param i The imagery info to add 677 */ 678 public void addRow(ImageryInfo i) { 679 layerInfo.add(i); 680 int p = getRowCount() - 1; 681 fireTableRowsInserted(p, p); 682 } 683 684 @Override 685 public void removeRow(int i) { 686 layerInfo.remove(getRow(i)); 687 fireTableRowsDeleted(i, i); 688 } 689 690 @Override 691 public int getRowCount() { 692 return layerInfo.getLayers().size(); 693 } 694 695 @Override 696 public Object getValueAt(int row, int column) { 697 ImageryInfo info = layerInfo.getLayers().get(row); 698 switch (column) { 699 case 0: 700 return info.getName(); 701 case 1: 702 return info.getExtendedUrl(); 703 default: 704 throw new ArrayIndexOutOfBoundsException(); 705 } 706 } 707 708 @Override 709 public void setValueAt(Object o, int row, int column) { 710 if (layerInfo.getLayers().size() <= row) return; 711 ImageryInfo info = layerInfo.getLayers().get(row); 712 switch (column) { 713 case 0: 714 info.setName((String) o); 715 info.clearId(); 716 break; 717 case 1: 718 info.setExtendedUrl((String) o); 719 info.clearId(); 720 break; 721 default: 722 throw new ArrayIndexOutOfBoundsException(); 723 } 724 } 725 } 726 727 /** 728 * The table model for the default imagery layer list 729 */ 730 public class ImageryDefaultLayerTableModel extends DefaultTableModel { 731 /** 732 * Constructs a new {@code ImageryDefaultLayerTableModel}. 733 */ 734 public ImageryDefaultLayerTableModel() { 735 setColumnIdentifiers(new String[]{"", tr("Menu Name (Default)"), tr("Imagery URL (Default)")}); 736 } 737 738 /** 739 * Returns the imagery info at the given row number. 740 * @param row The row number 741 * @return The imagery info at the given row number 742 */ 743 public ImageryInfo getRow(int row) { 744 return layerInfo.getDefaultLayers().get(row); 745 } 746 747 @Override 748 public int getRowCount() { 749 return layerInfo.getDefaultLayers().size(); 750 } 751 752 @Override 753 public Object getValueAt(int row, int column) { 754 ImageryInfo info = layerInfo.getDefaultLayers().get(row); 755 switch (column) { 756 case 0: 757 return info.getCountryCode(); 758 case 1: 759 return info; 760 case 2: 761 return info.getExtendedUrl(); 762 } 763 return null; 764 } 765 766 @Override 767 public boolean isCellEditable(int row, int column) { 768 return false; 769 } 770 } 771 772 private static boolean confirmEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) { 773 URL url = null; 774 try { 775 url = new URL(eulaUrl.replaceAll("\\{lang\\}", LanguageInfo.getWikiLanguagePrefix())); 776 JosmEditorPane htmlPane = null; 777 try { 778 htmlPane = new JosmEditorPane(url); 779 } catch (IOException e1) { 780 Main.trace(e1); 781 // give a second chance with a default Locale 'en' 782 try { 783 url = new URL(eulaUrl.replaceAll("\\{lang\\}", "")); 784 htmlPane = new JosmEditorPane(url); 785 } catch (IOException e2) { 786 Main.debug(e2); 787 JOptionPane.showMessageDialog(gui, tr("EULA license URL not available: {0}", eulaUrl)); 788 return false; 789 } 790 } 791 Box box = Box.createVerticalBox(); 792 htmlPane.setEditable(false); 793 JScrollPane scrollPane = new JScrollPane(htmlPane); 794 scrollPane.setPreferredSize(new Dimension(400, 400)); 795 box.add(scrollPane); 796 int option = JOptionPane.showConfirmDialog(Main.parent, box, tr("Please abort if you are not sure"), 797 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); 798 if (option == JOptionPane.YES_OPTION) 799 return true; 800 } catch (MalformedURLException e2) { 801 JOptionPane.showMessageDialog(gui, tr("Malformed URL for the EULA licence: {0}", eulaUrl)); 802 } 803 return false; 804 } 805 } 806 807 static class OffsetBookmarksPanel extends JPanel { 808 private final transient List<OffsetBookmark> bookmarks = OffsetBookmark.allBookmarks; 809 private final OffsetsBookmarksModel model = new OffsetsBookmarksModel(); 810 811 /** 812 * Constructs a new {@code OffsetBookmarksPanel}. 813 * @param gui the preferences tab pane 814 */ 815 OffsetBookmarksPanel(final PreferenceTabbedPane gui) { 816 super(new GridBagLayout()); 817 final JTable list = new JTable(model) { 818 @Override 819 public String getToolTipText(MouseEvent e) { 820 java.awt.Point p = e.getPoint(); 821 return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString(); 822 } 823 }; 824 JScrollPane scroll = new JScrollPane(list); 825 add(scroll, GBC.eol().fill(GridBagConstraints.BOTH)); 826 scroll.setPreferredSize(new Dimension(200, 200)); 827 828 TableColumnModel mod = list.getColumnModel(); 829 mod.getColumn(0).setPreferredWidth(150); 830 mod.getColumn(1).setPreferredWidth(200); 831 mod.getColumn(2).setPreferredWidth(300); 832 mod.getColumn(3).setPreferredWidth(150); 833 mod.getColumn(4).setPreferredWidth(150); 834 835 JPanel buttonPanel = new JPanel(new FlowLayout()); 836 837 JButton add = new JButton(tr("Add")); 838 buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0)); 839 add.addActionListener(e -> model.addRow(new OffsetBookmark(Main.getProjection().toCode(), "", "", 0, 0))); 840 841 JButton delete = new JButton(tr("Delete")); 842 buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0)); 843 delete.addActionListener(e -> { 844 if (list.getSelectedRow() == -1) { 845 JOptionPane.showMessageDialog(gui, tr("Please select the row to delete.")); 846 } else { 847 Integer i; 848 while ((i = list.getSelectedRow()) != -1) { 849 model.removeRow(i); 850 } 851 } 852 }); 853 854 add(buttonPanel, GBC.eol()); 855 } 856 857 /** 858 * The table model for imagery offsets list 859 */ 860 private class OffsetsBookmarksModel extends DefaultTableModel { 861 862 /** 863 * Constructs a new {@code OffsetsBookmarksModel}. 864 */ 865 OffsetsBookmarksModel() { 866 setColumnIdentifiers(new String[] {tr("Projection"), tr("Layer"), tr("Name"), tr("Easting"), tr("Northing")}); 867 } 868 869 private OffsetBookmark getRow(int row) { 870 return bookmarks.get(row); 871 } 872 873 private void addRow(OffsetBookmark i) { 874 bookmarks.add(i); 875 int p = getRowCount() - 1; 876 fireTableRowsInserted(p, p); 877 } 878 879 @Override 880 public void removeRow(int i) { 881 bookmarks.remove(getRow(i)); 882 fireTableRowsDeleted(i, i); 883 } 884 885 @Override 886 public int getRowCount() { 887 return bookmarks.size(); 888 } 889 890 @Override 891 public Object getValueAt(int row, int column) { 892 OffsetBookmark info = bookmarks.get(row); 893 switch (column) { 894 case 0: 895 if (info.projectionCode == null) return ""; 896 return info.projectionCode; 897 case 1: 898 return info.layerName; 899 case 2: 900 return info.name; 901 case 3: 902 return info.dx; 903 case 4: 904 return info.dy; 905 default: 906 throw new ArrayIndexOutOfBoundsException(); 907 } 908 } 909 910 @Override 911 public void setValueAt(Object o, int row, int column) { 912 OffsetBookmark info = bookmarks.get(row); 913 switch (column) { 914 case 1: 915 info.layerName = o.toString(); 916 break; 917 case 2: 918 info.name = o.toString(); 919 break; 920 case 3: 921 info.dx = Double.parseDouble((String) o); 922 break; 923 case 4: 924 info.dy = Double.parseDouble((String) o); 925 break; 926 default: 927 throw new ArrayIndexOutOfBoundsException(); 928 } 929 } 930 931 @Override 932 public boolean isCellEditable(int row, int column) { 933 return column >= 1; 934 } 935 } 936 } 937 938 /** 939 * Initializes imagery preferences. 940 */ 941 public static void initialize() { 942 ImageryLayerInfo.instance.load(true); 943 OffsetBookmark.loadBookmarks(); 944 Main.main.menu.imageryMenu.refreshImageryMenu(); 945 Main.main.menu.imageryMenu.refreshOffsetMenu(); 946 } 947}