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