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