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

Last change on this file since 14390 was 14390, checked in by simon04, 5 years ago

fix #15918 - Confusing English texts (thanks to Hb---)

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