001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.gui.preferences.imagery;
003
004import static org.openstreetmap.josm.tools.I18n.tr;
005
006import java.awt.Component;
007import java.awt.Dimension;
008import java.awt.FlowLayout;
009import java.awt.Font;
010import java.awt.GraphicsEnvironment;
011import java.awt.GridBagConstraints;
012import java.awt.GridBagLayout;
013import java.awt.event.MouseEvent;
014import java.util.List;
015import java.util.Objects;
016import java.util.stream.Collectors;
017
018import javax.swing.BorderFactory;
019import javax.swing.JButton;
020import javax.swing.JLabel;
021import javax.swing.JOptionPane;
022import javax.swing.JPanel;
023import javax.swing.JScrollPane;
024import javax.swing.JSeparator;
025import javax.swing.JTabbedPane;
026import javax.swing.JTable;
027import javax.swing.table.DefaultTableModel;
028import javax.swing.table.TableColumnModel;
029
030import org.openstreetmap.josm.data.coor.EastNorth;
031import org.openstreetmap.josm.data.imagery.ImageryInfo;
032import org.openstreetmap.josm.data.imagery.ImageryLayerInfo;
033import org.openstreetmap.josm.data.imagery.OffsetBookmark;
034import org.openstreetmap.josm.data.projection.ProjectionRegistry;
035import org.openstreetmap.josm.gui.MainApplication;
036import org.openstreetmap.josm.gui.download.DownloadDialog;
037import org.openstreetmap.josm.gui.help.HelpUtil;
038import org.openstreetmap.josm.gui.preferences.DefaultTabPreferenceSetting;
039import org.openstreetmap.josm.gui.preferences.PreferenceSetting;
040import org.openstreetmap.josm.gui.preferences.PreferenceSettingFactory;
041import org.openstreetmap.josm.gui.preferences.PreferenceTabbedPane;
042import org.openstreetmap.josm.gui.util.GuiHelper;
043import org.openstreetmap.josm.tools.GBC;
044import org.openstreetmap.josm.tools.Logging;
045
046/**
047 * Imagery preferences, including imagery providers, settings and offsets.
048 * @since 3715
049 */
050public final class ImageryPreference extends DefaultTabPreferenceSetting {
051
052    private ImageryProvidersPanel imageryProviders;
053    private ImageryLayerInfo layerInfo;
054
055    private final CommonSettingsPanel commonSettings = new CommonSettingsPanel();
056    private final WMSSettingsPanel wmsSettings = new WMSSettingsPanel();
057    private final TMSSettingsPanel tmsSettings = new TMSSettingsPanel();
058    private final CacheSettingsPanel cacheSettingsPanel = new CacheSettingsPanel();
059
060    /**
061     * Factory used to create a new {@code ImageryPreference}.
062     */
063    public static class Factory implements PreferenceSettingFactory {
064        @Override
065        public PreferenceSetting createPreferenceSetting() {
066            return new ImageryPreference();
067        }
068    }
069
070    private ImageryPreference() {
071        super(/* ICON(preferences/) */ "imagery", tr("Imagery preferences"),
072                tr("Modify list of imagery layers displayed in the Imagery menu"),
073                false, new JTabbedPane());
074    }
075
076    private static void addSettingsSection(final JPanel p, String name, JPanel section) {
077        addSettingsSection(p, name, section, GBC.eol());
078    }
079
080    private static void addSettingsSection(final JPanel p, String name, JPanel section, GBC gbc) {
081        final JLabel lbl = new JLabel(name);
082        lbl.setFont(lbl.getFont().deriveFont(Font.BOLD));
083        lbl.setLabelFor(section);
084        p.add(lbl, GBC.std());
085        p.add(new JSeparator(), GBC.eol().fill(GBC.HORIZONTAL).insets(5, 0, 0, 0));
086        p.add(section, gbc.insets(20, 5, 0, 10));
087    }
088
089    private Component buildSettingsPanel() {
090        final JPanel p = new JPanel(new GridBagLayout());
091        p.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
092
093        addSettingsSection(p, tr("Common Settings"), commonSettings);
094        addSettingsSection(p, tr("WMS Settings"), wmsSettings,
095                GBC.eol().fill(GBC.HORIZONTAL));
096        addSettingsSection(p, tr("TMS Settings"), tmsSettings,
097                GBC.eol().fill(GBC.HORIZONTAL));
098
099        p.add(new JPanel(), GBC.eol().fill(GBC.BOTH));
100        return GuiHelper.setDefaultIncrement(new JScrollPane(p));
101    }
102
103    @Override
104    public void addGui(final PreferenceTabbedPane gui) {
105        JPanel p = gui.createPreferenceTab(this);
106        JTabbedPane pane = getTabPane();
107        layerInfo = new ImageryLayerInfo(ImageryLayerInfo.instance);
108        imageryProviders = new ImageryProvidersPanel(gui, layerInfo);
109        pane.addTab(tr("Imagery providers"), imageryProviders);
110        pane.addTab(tr("Settings"), buildSettingsPanel());
111        pane.addTab(tr("Offset bookmarks"), new OffsetBookmarksPanel(gui));
112        pane.addTab(tr("Cache"), cacheSettingsPanel);
113        loadSettings();
114        p.add(pane, GBC.std().fill(GBC.BOTH));
115    }
116
117    /**
118     * Returns the imagery providers panel.
119     * @return The imagery providers panel.
120     */
121    public ImageryProvidersPanel getProvidersPanel() {
122        return imageryProviders;
123    }
124
125    private void loadSettings() {
126        commonSettings.loadSettings();
127        wmsSettings.loadSettings();
128        tmsSettings.loadSettings();
129        cacheSettingsPanel.loadSettings();
130    }
131
132    @Override
133    public boolean ok() {
134        layerInfo.save();
135        ImageryLayerInfo.instance.clear();
136        ImageryLayerInfo.instance.load(false);
137        MainApplication.getMenu().imageryMenu.refreshOffsetMenu();
138        OffsetBookmark.saveBookmarks();
139
140        if (!GraphicsEnvironment.isHeadless()) {
141            DownloadDialog.getInstance().refreshTileSources();
142        }
143
144        boolean commonRestartRequired = commonSettings.saveSettings();
145        boolean wmsRestartRequired = wmsSettings.saveSettings();
146        boolean tmsRestartRequired = tmsSettings.saveSettings();
147        boolean cacheRestartRequired = cacheSettingsPanel.saveSettings();
148
149        return commonRestartRequired || wmsRestartRequired || tmsRestartRequired || cacheRestartRequired;
150    }
151
152    /**
153     * Updates a server URL in the preferences dialog. Used by plugins.
154     *
155     * @param server
156     *            The server name
157     * @param url
158     *            The server URL
159     */
160    public void setServerUrl(String server, String url) {
161        for (int i = 0; i < imageryProviders.activeModel.getRowCount(); i++) {
162            if (server.equals(imageryProviders.activeModel.getValueAt(i, 0).toString())) {
163                imageryProviders.activeModel.setValueAt(url, i, 1);
164                return;
165            }
166        }
167        imageryProviders.activeModel.addRow(new String[] {server, url});
168    }
169
170    /**
171     * Gets a server URL in the preferences dialog. Used by plugins.
172     *
173     * @param server The server name
174     * @return The server URL
175     */
176    public String getServerUrl(String server) {
177        for (int i = 0; i < imageryProviders.activeModel.getRowCount(); i++) {
178            if (server.equals(imageryProviders.activeModel.getValueAt(i, 0).toString()))
179                return imageryProviders.activeModel.getValueAt(i, 1).toString();
180        }
181        return null;
182    }
183
184    static class OffsetBookmarksPanel extends JPanel {
185        private final OffsetsBookmarksModel model = new OffsetsBookmarksModel();
186
187        /**
188         * Constructs a new {@code OffsetBookmarksPanel}.
189         * @param gui the preferences tab pane
190         */
191        OffsetBookmarksPanel(final PreferenceTabbedPane gui) {
192            super(new GridBagLayout());
193            final JTable list = new JTable(model) {
194                @Override
195                public String getToolTipText(MouseEvent e) {
196                    java.awt.Point p = e.getPoint();
197                    return model.getValueAt(rowAtPoint(p), columnAtPoint(p)).toString();
198                }
199            };
200            JScrollPane scroll = new JScrollPane(list);
201            add(scroll, GBC.eol().fill(GridBagConstraints.BOTH));
202            scroll.setPreferredSize(new Dimension(200, 200));
203
204            TableColumnModel mod = list.getColumnModel();
205            mod.getColumn(0).setPreferredWidth(150);
206            mod.getColumn(1).setPreferredWidth(200);
207            mod.getColumn(2).setPreferredWidth(300);
208            mod.getColumn(3).setPreferredWidth(150);
209            mod.getColumn(4).setPreferredWidth(150);
210
211            JPanel buttonPanel = new JPanel(new FlowLayout());
212
213            JButton add = new JButton(tr("Add"));
214            buttonPanel.add(add, GBC.std().insets(0, 5, 0, 0));
215            add.addActionListener(e -> model.addRow(new OffsetBookmark(ProjectionRegistry.getProjection().toCode(), "", "", "", 0, 0)));
216
217            JButton delete = new JButton(tr("Delete"));
218            buttonPanel.add(delete, GBC.std().insets(0, 5, 0, 0));
219            delete.addActionListener(e -> {
220                if (list.getSelectedRow() == -1) {
221                    JOptionPane.showMessageDialog(gui, tr("Please select the row to delete."));
222                } else {
223                    Integer i;
224                    while ((i = list.getSelectedRow()) != -1) {
225                        model.removeRow(i);
226                    }
227                }
228            });
229
230            add(buttonPanel, GBC.eol());
231        }
232
233        /**
234         * The table model for imagery offsets list
235         */
236        private static class OffsetsBookmarksModel extends DefaultTableModel {
237
238            /**
239             * Constructs a new {@code OffsetsBookmarksModel}.
240             */
241            OffsetsBookmarksModel() {
242                setColumnIdentifiers(new String[] {tr("Projection"), tr("Layer"), tr("Name"), tr("Easting"), tr("Northing")});
243            }
244
245            private static OffsetBookmark getRow(int row) {
246                return OffsetBookmark.getBookmarkByIndex(row);
247            }
248
249            private void addRow(OffsetBookmark i) {
250                OffsetBookmark.addBookmark(i);
251                int p = getRowCount() - 1;
252                fireTableRowsInserted(p, p);
253            }
254
255            @Override
256            public void removeRow(int i) {
257                OffsetBookmark.removeBookmark(getRow(i));
258                fireTableRowsDeleted(i, i);
259            }
260
261            @Override
262            public int getRowCount() {
263                return OffsetBookmark.getBookmarksSize();
264            }
265
266            @Override
267            public Object getValueAt(int row, int column) {
268                OffsetBookmark info = OffsetBookmark.getBookmarkByIndex(row);
269                switch (column) {
270                case 0:
271                    if (info.getProjectionCode() == null) return "";
272                    return info.getProjectionCode();
273                case 1:
274                    return info.getImageryName();
275                case 2:
276                    return info.getName();
277                case 3:
278                    return info.getDisplacement().east();
279                case 4:
280                    return info.getDisplacement().north();
281                default:
282                    throw new ArrayIndexOutOfBoundsException(column);
283                }
284            }
285
286            @Override
287            public void setValueAt(Object o, int row, int column) {
288                OffsetBookmark info = OffsetBookmark.getBookmarkByIndex(row);
289                switch (column) {
290                case 1:
291                    String name = o.toString();
292                    info.setImageryName(name);
293                    List<ImageryInfo> layers = ImageryLayerInfo.instance.getLayers().stream()
294                            .filter(l -> Objects.equals(name, l.getName())).collect(Collectors.toList());
295                    if (layers.size() == 1) {
296                        info.setImageryId(layers.get(0).getId());
297                    } else {
298                        Logging.warn("Not a single layer for the name '" + info.getImageryName() + "': " + layers);
299                    }
300                    break;
301                case 2:
302                    info.setName(o.toString());
303                    break;
304                case 3:
305                    double dx = Double.parseDouble((String) o);
306                    info.setDisplacement(new EastNorth(dx, info.getDisplacement().north()));
307                    break;
308                case 4:
309                    double dy = Double.parseDouble((String) o);
310                    info.setDisplacement(new EastNorth(info.getDisplacement().east(), dy));
311                    break;
312                default:
313                    throw new ArrayIndexOutOfBoundsException(column);
314                }
315            }
316
317            @Override
318            public boolean isCellEditable(int row, int column) {
319                return column >= 1;
320            }
321        }
322    }
323
324    /**
325     * Initializes imagery preferences.
326     */
327    public static void initialize() {
328        ImageryLayerInfo.instance.load(false);
329        OffsetBookmark.loadBookmarks();
330        MainApplication.getMenu().imageryMenu.refreshImageryMenu();
331        MainApplication.getMenu().imageryMenu.refreshOffsetMenu();
332    }
333
334    @Override
335    public String getHelpContext() {
336        return HelpUtil.ht("/Preferences/Imagery");
337    }
338}