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

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

see #16301 - display tooltips for all columns

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