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}