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

Last change on this file since 14153 was 14153, checked in by Don-vip, 6 years ago

see #15229 - deprecate Main.parent and Main itself

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