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}