source: josm/trunk/src/org/openstreetmap/josm/gui/preferences/imagery/ImageryPreference.java@ 12093

Last change on this file since 12093 was 12093, checked in by bastiK, 7 years ago

fixed #14734 - Handling imagery offsets when reprojecting

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