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.ActionListener; 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.event.TableModelEvent; 044import javax.swing.event.TableModelListener; 045import javax.swing.table.DefaultTableCellRenderer; 046import javax.swing.table.DefaultTableModel; 047import javax.swing.table.TableColumnModel; 048 049import org.openstreetmap.gui.jmapviewer.Coordinate; 050import org.openstreetmap.gui.jmapviewer.JMapViewer; 051import org.openstreetmap.gui.jmapviewer.MapPolygonImpl; 052import org.openstreetmap.gui.jmapviewer.MapRectangleImpl; 053import org.openstreetmap.gui.jmapviewer.interfaces.MapPolygon; 054import org.openstreetmap.gui.jmapviewer.interfaces.MapRectangle; 055import org.openstreetmap.josm.Main; 056import org.openstreetmap.josm.data.imagery.ImageryInfo; 057import org.openstreetmap.josm.data.imagery.ImageryInfo.ImageryBounds; 058import org.openstreetmap.josm.data.imagery.ImageryLayerInfo; 059import org.openstreetmap.josm.data.imagery.OffsetBookmark; 060import org.openstreetmap.josm.data.imagery.Shape; 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 final transient List<ImageryInfo> layers; 245 246 ImageryURLTableCellRenderer(List<ImageryInfo> layers) { 247 this.layers = layers; 248 } 249 250 @Override 251 public Component getTableCellRendererComponent(JTable table, Object value, boolean 252 isSelected, boolean hasFocus, int row, int column) { 253 JLabel label = (JLabel) super.getTableCellRendererComponent( 254 table, value, isSelected, hasFocus, row, column); 255 GuiHelper.setBackgroundReadable(label, UIManager.getColor("Table.background")); 256 if (value != null) { // Fix #8159 257 String t = value.toString(); 258 for (ImageryInfo l : layers) { 259 if (l.getExtendedUrl().equals(t)) { 260 GuiHelper.setBackgroundReadable(label, Main.pref.getColor( 261 marktr("Imagery Background: Default"), 262 new Color(200, 255, 200))); 263 break; 264 } 265 } 266 label.setToolTipText((String) value); 267 } 268 return label; 269 } 270 } 271 272 /** 273 * class to render the name information of Imagery source 274 * @since 8064 275 */ 276 private static class ImageryNameTableCellRenderer extends DefaultTableCellRenderer { 277 @Override 278 public Component getTableCellRendererComponent(JTable table, Object value, boolean 279 isSelected, boolean hasFocus, int row, int column) { 280 ImageryInfo info = (ImageryInfo) value; 281 JLabel label = (JLabel) super.getTableCellRendererComponent( 282 table, info == null ? null : info.getName(), isSelected, hasFocus, row, column); 283 GuiHelper.setBackgroundReadable(label, UIManager.getColor("Table.background")); 284 if (info != null) { 285 label.setToolTipText(info.getToolTipText()); 286 } 287 return label; 288 } 289 } 290 291 /** 292 * Constructs a new {@code ImageryProvidersPanel}. 293 * @param gui The parent preference tab pane 294 * @param layerInfoArg The list of imagery entries to display 295 */ 296 public ImageryProvidersPanel(final PreferenceTabbedPane gui, ImageryLayerInfo layerInfoArg) { 297 super(new GridBagLayout()); 298 this.gui = gui; 299 this.layerInfo = layerInfoArg; 300 this.activeModel = new ImageryLayerTableModel(); 301 302 activeTable = new JTable(activeModel) { 303 @Override 304 public String getToolTipText(MouseEvent e) { 305 java.awt.Point p = e.getPoint(); 306 return activeModel.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString(); 307 } 308 }; 309 activeTable.putClientProperty("terminateEditOnFocusLost", Boolean.TRUE); 310 311 defaultModel = new ImageryDefaultLayerTableModel(); 312 defaultTable = new JTable(defaultModel); 313 314 defaultModel.addTableModelListener( 315 new TableModelListener() { 316 @Override 317 public void tableChanged(TableModelEvent e) { 318 activeTable.repaint(); 319 } 320 } 321 ); 322 323 activeModel.addTableModelListener( 324 new TableModelListener() { 325 @Override 326 public void tableChanged(TableModelEvent e) { 327 defaultTable.repaint(); 328 } 329 } 330 ); 331 332 TableColumnModel mod = defaultTable.getColumnModel(); 333 mod.getColumn(2).setPreferredWidth(800); 334 mod.getColumn(2).setCellRenderer(new ImageryURLTableCellRenderer(layerInfo.getLayers())); 335 mod.getColumn(1).setPreferredWidth(400); 336 mod.getColumn(1).setCellRenderer(new ImageryNameTableCellRenderer()); 337 mod.getColumn(0).setPreferredWidth(50); 338 339 mod = activeTable.getColumnModel(); 340 mod.getColumn(1).setPreferredWidth(800); 341 mod.getColumn(1).setCellRenderer(new ImageryURLTableCellRenderer(layerInfo.getDefaultLayers())); 342 mod.getColumn(0).setPreferredWidth(200); 343 344 RemoveEntryAction remove = new RemoveEntryAction(); 345 activeTable.getSelectionModel().addListSelectionListener(remove); 346 347 add(new JLabel(tr("Available default entries:")), GBC.eol().insets(5, 5, 0, 0)); 348 // Add default item list 349 JScrollPane scrolldef = new JScrollPane(defaultTable); 350 scrolldef.setPreferredSize(new Dimension(200, 200)); 351 add(scrolldef, GBC.std().insets(0, 5, 0, 0).fill(GridBagConstraints.BOTH).weight(1.0, 0.6).insets(5, 0, 0, 0)); 352 353 // Add default item map 354 defaultMap = new JMapViewer(); 355 defaultMap.setZoomContolsVisible(false); 356 defaultMap.setMinimumSize(new Dimension(100, 200)); 357 add(defaultMap, GBC.std().insets(5, 5, 0, 0).fill(GridBagConstraints.BOTH).weight(0.33, 0.6).insets(5, 0, 0, 0)); 358 359 defaultTableListener = new DefListSelectionListener(); 360 defaultTable.getSelectionModel().addListSelectionListener(defaultTableListener); 361 362 defaultToolbar = new JToolBar(JToolBar.VERTICAL); 363 defaultToolbar.setFloatable(false); 364 defaultToolbar.setBorderPainted(false); 365 defaultToolbar.setOpaque(false); 366 defaultToolbar.add(new ReloadAction()); 367 add(defaultToolbar, GBC.eol().anchor(GBC.SOUTH).insets(0, 0, 5, 0)); 368 369 ActivateAction activate = new ActivateAction(); 370 defaultTable.getSelectionModel().addListSelectionListener(activate); 371 JButton btnActivate = new JButton(activate); 372 373 middleToolbar = new JToolBar(JToolBar.HORIZONTAL); 374 middleToolbar.setFloatable(false); 375 middleToolbar.setBorderPainted(false); 376 middleToolbar.setOpaque(false); 377 middleToolbar.add(btnActivate); 378 add(middleToolbar, GBC.eol().anchor(GBC.CENTER).insets(5, 15, 5, 0)); 379 380 add(Box.createHorizontalGlue(), GBC.eol().fill(GridBagConstraints.HORIZONTAL)); 381 382 add(new JLabel(tr("Selected entries:")), GBC.eol().insets(5, 0, 0, 0)); 383 JScrollPane scroll = new JScrollPane(activeTable); 384 add(scroll, GBC.std().fill(GridBagConstraints.BOTH).span(GridBagConstraints.RELATIVE).weight(1.0, 0.4).insets(5, 0, 0, 5)); 385 scroll.setPreferredSize(new Dimension(200, 200)); 386 387 activeToolbar = new JToolBar(JToolBar.VERTICAL); 388 activeToolbar.setFloatable(false); 389 activeToolbar.setBorderPainted(false); 390 activeToolbar.setOpaque(false); 391 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMS)); 392 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.TMS)); 393 activeToolbar.add(new NewEntryAction(ImageryInfo.ImageryType.WMTS)); 394 //activeToolbar.add(edit); TODO 395 activeToolbar.add(remove); 396 add(activeToolbar, GBC.eol().anchor(GBC.NORTH).insets(0, 0, 5, 5)); 397 } 398 399 // Listener of default providers list selection 400 private final class DefListSelectionListener implements ListSelectionListener { 401 // The current drawn rectangles and polygons 402 private final Map<Integer, MapRectangle> mapRectangles; 403 private final Map<Integer, List<MapPolygon>> mapPolygons; 404 405 private DefListSelectionListener() { 406 this.mapRectangles = new HashMap<>(); 407 this.mapPolygons = new HashMap<>(); 408 } 409 410 private void clearMap() { 411 defaultMap.removeAllMapRectangles(); 412 defaultMap.removeAllMapPolygons(); 413 mapRectangles.clear(); 414 mapPolygons.clear(); 415 } 416 417 @Override 418 public void valueChanged(ListSelectionEvent e) { 419 // First index can be set to -1 when the list is refreshed, so discard all map rectangles and polygons 420 if (e.getFirstIndex() == -1) { 421 clearMap(); 422 } else if (!e.getValueIsAdjusting()) { 423 // Only process complete (final) selection events 424 for (int i = e.getFirstIndex(); i <= e.getLastIndex(); i++) { 425 updateBoundsAndShapes(i); 426 } 427 // If needed, adjust map to show all map rectangles and polygons 428 if (!mapRectangles.isEmpty() || !mapPolygons.isEmpty()) { 429 defaultMap.setDisplayToFitMapElements(false, true, true); 430 defaultMap.zoomOut(); 431 } 432 } 433 } 434 435 private void updateBoundsAndShapes(int i) { 436 ImageryBounds bounds = defaultModel.getRow(i).getBounds(); 437 if (bounds != null) { 438 List<Shape> shapes = bounds.getShapes(); 439 if (shapes != null && !shapes.isEmpty()) { 440 if (defaultTable.getSelectionModel().isSelectedIndex(i)) { 441 if (!mapPolygons.containsKey(i)) { 442 List<MapPolygon> list = new ArrayList<>(); 443 mapPolygons.put(i, list); 444 // Add new map polygons 445 for (Shape shape : shapes) { 446 MapPolygon polygon = new MapPolygonImpl(shape.getPoints()); 447 list.add(polygon); 448 defaultMap.addMapPolygon(polygon); 449 } 450 } 451 } else if (mapPolygons.containsKey(i)) { 452 // Remove previously drawn map polygons 453 for (MapPolygon polygon : mapPolygons.get(i)) { 454 defaultMap.removeMapPolygon(polygon); 455 } 456 mapPolygons.remove(i); 457 } 458 // Only display bounds when no polygons (shapes) are defined for this provider 459 } else { 460 if (defaultTable.getSelectionModel().isSelectedIndex(i)) { 461 if (!mapRectangles.containsKey(i)) { 462 // Add new map rectangle 463 Coordinate topLeft = new Coordinate(bounds.getMaxLat(), bounds.getMinLon()); 464 Coordinate bottomRight = new Coordinate(bounds.getMinLat(), bounds.getMaxLon()); 465 MapRectangle rectangle = new MapRectangleImpl(topLeft, bottomRight); 466 mapRectangles.put(i, rectangle); 467 defaultMap.addMapRectangle(rectangle); 468 } 469 } else if (mapRectangles.containsKey(i)) { 470 // Remove previously drawn map rectangle 471 defaultMap.removeMapRectangle(mapRectangles.get(i)); 472 mapRectangles.remove(i); 473 } 474 } 475 } 476 } 477 } 478 479 private class NewEntryAction extends AbstractAction { 480 481 private final ImageryInfo.ImageryType type; 482 483 NewEntryAction(ImageryInfo.ImageryType type) { 484 putValue(NAME, type.toString()); 485 putValue(SHORT_DESCRIPTION, tr("Add a new {0} entry by entering the URL", type.toString())); 486 String icon = /* ICON(dialogs/) */ "add"; 487 switch (type) { 488 case WMS: 489 icon = /* ICON(dialogs/) */ "add_wms"; 490 break; 491 case TMS: 492 icon = /* ICON(dialogs/) */ "add_tms"; 493 break; 494 case WMTS: 495 icon = /* ICON(dialogs/) */ "add_wmts"; 496 break; 497 default: 498 break; 499 } 500 putValue(SMALL_ICON, ImageProvider.get("dialogs", icon)); 501 this.type = type; 502 } 503 504 @Override 505 public void actionPerformed(ActionEvent evt) { 506 final AddImageryPanel p; 507 switch (type) { 508 case WMS: 509 p = new AddWMSLayerPanel(); 510 break; 511 case TMS: 512 p = new AddTMSLayerPanel(); 513 break; 514 case WMTS: 515 p = new AddWMTSLayerPanel(); 516 break; 517 default: 518 throw new IllegalStateException("Type " + type + " not supported"); 519 } 520 521 final AddImageryDialog addDialog = new AddImageryDialog(gui, p); 522 addDialog.showDialog(); 523 524 if (addDialog.getValue() == 1) { 525 try { 526 activeModel.addRow(p.getImageryInfo()); 527 } catch (IllegalArgumentException ex) { 528 if (ex.getMessage() == null || ex.getMessage().isEmpty()) 529 throw ex; 530 else { 531 JOptionPane.showMessageDialog(Main.parent, 532 ex.getMessage(), tr("Error"), 533 JOptionPane.ERROR_MESSAGE); 534 } 535 } 536 } 537 } 538 } 539 540 private class RemoveEntryAction extends AbstractAction implements ListSelectionListener { 541 542 /** 543 * Constructs a new {@code RemoveEntryAction}. 544 */ 545 RemoveEntryAction() { 546 putValue(NAME, tr("Remove")); 547 putValue(SHORT_DESCRIPTION, tr("Remove entry")); 548 putValue(SMALL_ICON, ImageProvider.get("dialogs", "delete")); 549 updateEnabledState(); 550 } 551 552 protected final void updateEnabledState() { 553 setEnabled(activeTable.getSelectedRowCount() > 0); 554 } 555 556 @Override 557 public void valueChanged(ListSelectionEvent e) { 558 updateEnabledState(); 559 } 560 561 @Override 562 public void actionPerformed(ActionEvent e) { 563 Integer i; 564 while ((i = activeTable.getSelectedRow()) != -1) { 565 activeModel.removeRow(i); 566 } 567 } 568 } 569 570 private class ActivateAction extends AbstractAction implements ListSelectionListener { 571 572 /** 573 * Constructs a new {@code ActivateAction}. 574 */ 575 ActivateAction() { 576 putValue(NAME, tr("Activate")); 577 putValue(SHORT_DESCRIPTION, tr("copy selected defaults")); 578 putValue(SMALL_ICON, ImageProvider.get("preferences", "activate-down")); 579 } 580 581 protected void updateEnabledState() { 582 setEnabled(defaultTable.getSelectedRowCount() > 0); 583 } 584 585 @Override 586 public void valueChanged(ListSelectionEvent e) { 587 updateEnabledState(); 588 } 589 590 @Override 591 public void actionPerformed(ActionEvent e) { 592 int[] lines = defaultTable.getSelectedRows(); 593 if (lines.length == 0) { 594 JOptionPane.showMessageDialog( 595 gui, 596 tr("Please select at least one row to copy."), 597 tr("Information"), 598 JOptionPane.INFORMATION_MESSAGE); 599 return; 600 } 601 602 Set<String> acceptedEulas = new HashSet<>(); 603 604 outer: 605 for (int line : lines) { 606 ImageryInfo info = defaultModel.getRow(line); 607 608 // Check if an entry with exactly the same values already exists 609 for (int j = 0; j < activeModel.getRowCount(); j++) { 610 if (info.equalsBaseValues(activeModel.getRow(j))) { 611 // Select the already existing row so the user has 612 // some feedback in case an entry exists 613 activeTable.getSelectionModel().setSelectionInterval(j, j); 614 activeTable.scrollRectToVisible(activeTable.getCellRect(j, 0, true)); 615 continue outer; 616 } 617 } 618 619 String eulaURL = info.getEulaAcceptanceRequired(); 620 // If set and not already accepted, ask for EULA acceptance 621 if (eulaURL != null && !acceptedEulas.contains(eulaURL)) { 622 if (confirmEulaAcceptance(gui, eulaURL)) { 623 acceptedEulas.add(eulaURL); 624 } else { 625 continue outer; 626 } 627 } 628 629 activeModel.addRow(new ImageryInfo(info)); 630 int lastLine = activeModel.getRowCount() - 1; 631 activeTable.getSelectionModel().setSelectionInterval(lastLine, lastLine); 632 activeTable.scrollRectToVisible(activeTable.getCellRect(lastLine, 0, true)); 633 } 634 } 635 } 636 637 private class ReloadAction extends AbstractAction { 638 639 /** 640 * Constructs a new {@code ReloadAction}. 641 */ 642 ReloadAction() { 643 putValue(SHORT_DESCRIPTION, tr("Update default entries")); 644 putValue(SMALL_ICON, ImageProvider.get("dialogs", "refresh")); 645 } 646 647 @Override 648 public void actionPerformed(ActionEvent evt) { 649 layerInfo.loadDefaults(true, false, false); 650 defaultModel.fireTableDataChanged(); 651 defaultTable.getSelectionModel().clearSelection(); 652 defaultTableListener.clearMap(); 653 /* loading new file may change active layers */ 654 activeModel.fireTableDataChanged(); 655 } 656 } 657 658 /** 659 * The table model for imagery layer list 660 */ 661 public class ImageryLayerTableModel extends DefaultTableModel { 662 /** 663 * Constructs a new {@code ImageryLayerTableModel}. 664 */ 665 public ImageryLayerTableModel() { 666 setColumnIdentifiers(new String[] {tr("Menu Name"), tr("Imagery URL")}); 667 } 668 669 /** 670 * Returns the imagery info at the given row number. 671 * @param row The row number 672 * @return The imagery info at the given row number 673 */ 674 public ImageryInfo getRow(int row) { 675 return layerInfo.getLayers().get(row); 676 } 677 678 /** 679 * Adds a new imagery info as the last row. 680 * @param i The imagery info to add 681 */ 682 public void addRow(ImageryInfo i) { 683 layerInfo.add(i); 684 int p = getRowCount() - 1; 685 fireTableRowsInserted(p, p); 686 } 687 688 @Override 689 public void removeRow(int i) { 690 layerInfo.remove(getRow(i)); 691 fireTableRowsDeleted(i, i); 692 } 693 694 @Override 695 public int getRowCount() { 696 return layerInfo.getLayers().size(); 697 } 698 699 @Override 700 public Object getValueAt(int row, int column) { 701 ImageryInfo info = layerInfo.getLayers().get(row); 702 switch (column) { 703 case 0: 704 return info.getName(); 705 case 1: 706 return info.getExtendedUrl(); 707 default: 708 throw new ArrayIndexOutOfBoundsException(); 709 } 710 } 711 712 @Override 713 public void setValueAt(Object o, int row, int column) { 714 if (layerInfo.getLayers().size() <= row) return; 715 ImageryInfo info = layerInfo.getLayers().get(row); 716 switch (column) { 717 case 0: 718 info.setName((String) o); 719 info.clearId(); 720 break; 721 case 1: 722 info.setExtendedUrl((String) o); 723 info.clearId(); 724 break; 725 default: 726 throw new ArrayIndexOutOfBoundsException(); 727 } 728 } 729 } 730 731 /** 732 * The table model for the default imagery layer list 733 */ 734 public class ImageryDefaultLayerTableModel extends DefaultTableModel { 735 /** 736 * Constructs a new {@code ImageryDefaultLayerTableModel}. 737 */ 738 public ImageryDefaultLayerTableModel() { 739 setColumnIdentifiers(new String[]{"", tr("Menu Name (Default)"), tr("Imagery URL (Default)")}); 740 } 741 742 /** 743 * Returns the imagery info at the given row number. 744 * @param row The row number 745 * @return The imagery info at the given row number 746 */ 747 public ImageryInfo getRow(int row) { 748 return layerInfo.getDefaultLayers().get(row); 749 } 750 751 @Override 752 public int getRowCount() { 753 return layerInfo.getDefaultLayers().size(); 754 } 755 756 @Override 757 public Object getValueAt(int row, int column) { 758 ImageryInfo info = layerInfo.getDefaultLayers().get(row); 759 switch (column) { 760 case 0: 761 return info.getCountryCode(); 762 case 1: 763 return info; 764 case 2: 765 return info.getExtendedUrl(); 766 } 767 return null; 768 } 769 770 @Override 771 public boolean isCellEditable(int row, int column) { 772 return false; 773 } 774 } 775 776 private static boolean confirmEulaAcceptance(PreferenceTabbedPane gui, String eulaUrl) { 777 URL url = null; 778 try { 779 url = new URL(eulaUrl.replaceAll("\\{lang\\}", LanguageInfo.getWikiLanguagePrefix())); 780 JosmEditorPane htmlPane = null; 781 try { 782 htmlPane = new JosmEditorPane(url); 783 } catch (IOException e1) { 784 // give a second chance with a default Locale 'en' 785 try { 786 url = new URL(eulaUrl.replaceAll("\\{lang\\}", "")); 787 htmlPane = new JosmEditorPane(url); 788 } catch (IOException e2) { 789 JOptionPane.showMessageDialog(gui, tr("EULA license URL not available: {0}", eulaUrl)); 790 return false; 791 } 792 } 793 Box box = Box.createVerticalBox(); 794 htmlPane.setEditable(false); 795 JScrollPane scrollPane = new JScrollPane(htmlPane); 796 scrollPane.setPreferredSize(new Dimension(400, 400)); 797 box.add(scrollPane); 798 int option = JOptionPane.showConfirmDialog(Main.parent, box, tr("Please abort if you are not sure"), 799 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); 800 if (option == JOptionPane.YES_OPTION) 801 return true; 802 } catch (MalformedURLException e2) { 803 JOptionPane.showMessageDialog(gui, tr("Malformed URL for the EULA licence: {0}", eulaUrl)); 804 } 805 return false; 806 } 807 } 808 809 static class OffsetBookmarksPanel extends JPanel { 810 private final transient List<OffsetBookmark> bookmarks = OffsetBookmark.allBookmarks; 811 private final OffsetsBookmarksModel model = new OffsetsBookmarksModel(); 812 813 /** 814 * Constructs a new {@code OffsetBookmarksPanel}. 815 * @param gui the preferences tab pane 816 */ 817 OffsetBookmarksPanel(final PreferenceTabbedPane gui) { 818 super(new GridBagLayout()); 819 final JTable list = new JTable(model) { 820 @Override 821 public String getToolTipText(MouseEvent e) { 822 java.awt.Point p = e.getPoint(); 823 return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString(); 824 } 825 }; 826 JScrollPane scroll = new JScrollPane(list); 827 add(scroll, GBC.eol().fill(GridBagConstraints.BOTH)); 828 scroll.setPreferredSize(new Dimension(200, 200)); 829 830 TableColumnModel mod = list.getColumnModel(); 831 mod.getColumn(0).setPreferredWidth(150); 832 mod.getColumn(1).setPreferredWidth(200); 833 mod.getColumn(2).setPreferredWidth(300); 834 mod.getColumn(3).setPreferredWidth(150); 835 mod.getColumn(4).setPreferredWidth(150); 836 837 JPanel buttonPanel = new JPanel(new FlowLayout()); 838 839 JButton add = new JButton(tr("Add")); 840 buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0)); 841 add.addActionListener(new ActionListener() { 842 @Override 843 public void actionPerformed(ActionEvent e) { 844 OffsetBookmark b = new OffsetBookmark(Main.getProjection().toCode(), "", "", 0, 0); 845 model.addRow(b); 846 } 847 }); 848 849 JButton delete = new JButton(tr("Delete")); 850 buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0)); 851 delete.addActionListener(new ActionListener() { 852 @Override 853 public void actionPerformed(ActionEvent e) { 854 if (list.getSelectedRow() == -1) { 855 JOptionPane.showMessageDialog(gui, tr("Please select the row to delete.")); 856 } else { 857 Integer i; 858 while ((i = list.getSelectedRow()) != -1) { 859 model.removeRow(i); 860 } 861 } 862 } 863 }); 864 865 add(buttonPanel, GBC.eol()); 866 } 867 868 /** 869 * The table model for imagery offsets list 870 */ 871 private class OffsetsBookmarksModel extends DefaultTableModel { 872 873 /** 874 * Constructs a new {@code OffsetsBookmarksModel}. 875 */ 876 OffsetsBookmarksModel() { 877 setColumnIdentifiers(new String[] {tr("Projection"), tr("Layer"), tr("Name"), tr("Easting"), tr("Northing")}); 878 } 879 880 private OffsetBookmark getRow(int row) { 881 return bookmarks.get(row); 882 } 883 884 private void addRow(OffsetBookmark i) { 885 bookmarks.add(i); 886 int p = getRowCount() - 1; 887 fireTableRowsInserted(p, p); 888 } 889 890 @Override 891 public void removeRow(int i) { 892 bookmarks.remove(getRow(i)); 893 fireTableRowsDeleted(i, i); 894 } 895 896 @Override 897 public int getRowCount() { 898 return bookmarks.size(); 899 } 900 901 @Override 902 public Object getValueAt(int row, int column) { 903 OffsetBookmark info = bookmarks.get(row); 904 switch (column) { 905 case 0: 906 if (info.projectionCode == null) return ""; 907 return info.projectionCode; 908 case 1: 909 return info.layerName; 910 case 2: 911 return info.name; 912 case 3: 913 return info.dx; 914 case 4: 915 return info.dy; 916 default: 917 throw new ArrayIndexOutOfBoundsException(); 918 } 919 } 920 921 @Override 922 public void setValueAt(Object o, int row, int column) { 923 OffsetBookmark info = bookmarks.get(row); 924 switch (column) { 925 case 1: 926 info.layerName = o.toString(); 927 break; 928 case 2: 929 info.name = o.toString(); 930 break; 931 case 3: 932 info.dx = Double.parseDouble((String) o); 933 break; 934 case 4: 935 info.dy = Double.parseDouble((String) o); 936 break; 937 default: 938 throw new ArrayIndexOutOfBoundsException(); 939 } 940 } 941 942 @Override 943 public boolean isCellEditable(int row, int column) { 944 return column >= 1; 945 } 946 } 947 } 948 949 /** 950 * Initializes imagery preferences. 951 */ 952 public static void initialize() { 953 ImageryLayerInfo.instance.load(true); 954 OffsetBookmark.loadBookmarks(); 955 Main.main.menu.imageryMenu.refreshImageryMenu(); 956 Main.main.menu.imageryMenu.refreshOffsetMenu(); 957 } 958}