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