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