001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006 007import java.awt.BorderLayout; 008import java.awt.Component; 009import java.awt.GridLayout; 010import java.awt.Rectangle; 011import java.awt.event.ActionEvent; 012import java.awt.event.ActionListener; 013import java.awt.event.FocusEvent; 014import java.awt.event.FocusListener; 015import java.awt.event.KeyEvent; 016import java.util.ArrayList; 017import java.util.Arrays; 018import java.util.Collection; 019import java.util.Collections; 020import java.util.Deque; 021import java.util.LinkedList; 022import java.util.concurrent.Future; 023 024import javax.swing.AbstractAction; 025import javax.swing.Action; 026import javax.swing.ActionMap; 027import javax.swing.JButton; 028import javax.swing.JComponent; 029import javax.swing.JLabel; 030import javax.swing.JMenuItem; 031import javax.swing.JOptionPane; 032import javax.swing.JPanel; 033import javax.swing.JPopupMenu; 034import javax.swing.JScrollPane; 035import javax.swing.plaf.basic.BasicArrowButton; 036 037import org.openstreetmap.josm.Main; 038import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask; 039import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler; 040import org.openstreetmap.josm.data.Bounds; 041import org.openstreetmap.josm.data.preferences.CollectionProperty; 042import org.openstreetmap.josm.data.preferences.IntegerProperty; 043import org.openstreetmap.josm.data.preferences.StringProperty; 044import org.openstreetmap.josm.gui.HelpAwareOptionPane; 045import org.openstreetmap.josm.gui.download.DownloadDialog; 046import org.openstreetmap.josm.gui.util.GuiHelper; 047import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 048import org.openstreetmap.josm.gui.widgets.JosmTextArea; 049import org.openstreetmap.josm.io.OverpassDownloadReader; 050import org.openstreetmap.josm.tools.GBC; 051import org.openstreetmap.josm.tools.OverpassTurboQueryWizard; 052import org.openstreetmap.josm.tools.Shortcut; 053import org.openstreetmap.josm.tools.Utils; 054 055/** 056 * Download map data from Overpass API server. 057 * @since 8684 058 */ 059public class OverpassDownloadAction extends JosmAction { 060 061 /** 062 * Constructs a new {@code OverpassDownloadAction}. 063 */ 064 public OverpassDownloadAction() { 065 super(tr("Download from Overpass API ..."), "download-overpass", tr("Download map data from Overpass API server."), 066 // CHECKSTYLE.OFF: LineLength 067 Shortcut.registerShortcut("file:download-overpass", tr("File: {0}", tr("Download from Overpass API ...")), KeyEvent.VK_DOWN, Shortcut.ALT_SHIFT), 068 // CHECKSTYLE.ON: LineLength 069 true, "overpassdownload/download", true); 070 putValue("help", ht("/Action/OverpassDownload")); 071 } 072 073 @Override 074 public void actionPerformed(ActionEvent e) { 075 OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance(); 076 dialog.restoreSettings(); 077 dialog.setVisible(true); 078 if (!dialog.isCanceled()) { 079 dialog.rememberSettings(); 080 Bounds area = dialog.getSelectedDownloadArea(); 081 DownloadOsmTask task = new DownloadOsmTask(); 082 Future<?> future = task.download( 083 new OverpassDownloadReader(area, dialog.getOverpassServer(), dialog.getOverpassQuery()), 084 dialog.isNewLayerRequired(), area, null); 085 Main.worker.submit(new PostDownloadHandler(task, future)); 086 } 087 } 088 089 private static final class DisableActionsFocusListener implements FocusListener { 090 091 private final ActionMap actionMap; 092 093 private DisableActionsFocusListener(ActionMap actionMap) { 094 this.actionMap = actionMap; 095 } 096 097 @Override 098 public void focusGained(FocusEvent e) { 099 enableActions(false); 100 } 101 102 @Override 103 public void focusLost(FocusEvent e) { 104 enableActions(true); 105 } 106 107 private void enableActions(boolean enabled) { 108 for (Object key : actionMap.allKeys()) { 109 Action action = actionMap.get(key); 110 if (action != null) { 111 action.setEnabled(enabled); 112 } 113 } 114 } 115 } 116 117 private static final class OverpassDownloadDialog extends DownloadDialog { 118 119 private HistoryComboBox overpassServer; 120 private HistoryComboBox overpassWizard; 121 private JosmTextArea overpassQuery; 122 private static OverpassDownloadDialog instance; 123 private static final StringProperty OVERPASS_SERVER = new StringProperty("download.overpass.server", "http://overpass-api.de/api/"); 124 private static final CollectionProperty OVERPASS_SERVER_HISTORY = new CollectionProperty("download.overpass.servers", 125 Arrays.asList("http://overpass-api.de/api/", "http://overpass.osm.rambler.ru/cgi/")); 126 private static final CollectionProperty OVERPASS_WIZARD_HISTORY = new CollectionProperty("download.overpass.wizard", 127 new ArrayList<String>()); 128 129 private OverpassDownloadDialog(Component parent) { 130 super(parent, ht("/Action/OverpassDownload")); 131 cbDownloadOsmData.setEnabled(false); 132 cbDownloadOsmData.setSelected(false); 133 cbDownloadGpxData.setVisible(false); 134 cbDownloadNotes.setVisible(false); 135 cbStartup.setVisible(false); 136 } 137 138 public static OverpassDownloadDialog getInstance() { 139 if (instance == null) { 140 instance = new OverpassDownloadDialog(Main.parent); 141 } 142 return instance; 143 } 144 145 @Override 146 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) { 147 148 DisableActionsFocusListener disableActionsFocusListener = 149 new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap()); 150 151 pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes 152 153 final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard"); 154 overpassWizard = new HistoryComboBox(); 155 overpassWizard.setToolTipText(tooltip); 156 overpassWizard.getEditor().getEditorComponent().addFocusListener(disableActionsFocusListener); 157 final JButton buildQuery = new JButton(tr("Build query")); 158 buildQuery.addActionListener(new AbstractAction() { 159 @Override 160 public void actionPerformed(ActionEvent e) { 161 final String overpassWizardText = overpassWizard.getText(); 162 try { 163 overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText)); 164 } catch (OverpassTurboQueryWizard.ParseException ex) { 165 HelpAwareOptionPane.showOptionDialog( 166 Main.parent, 167 tr("<html>The Overpass wizard could not parse the following query:" 168 + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))), 169 tr("Parse error"), 170 JOptionPane.ERROR_MESSAGE, 171 null 172 ); 173 } 174 } 175 }); 176 buildQuery.setToolTipText(tooltip); 177 pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5)); 178 pnl.add(overpassWizard, GBC.eol().fill(GBC.HORIZONTAL)); 179 180 overpassQuery = new JosmTextArea("", 8, 80); 181 overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery)); 182 overpassQuery.addFocusListener(disableActionsFocusListener); 183 JScrollPane scrollPane = new JScrollPane(overpassQuery); 184 final JPanel pane = new JPanel(new BorderLayout()); 185 final BasicArrowButton arrowButton = new BasicArrowButton(BasicArrowButton.SOUTH); 186 arrowButton.addActionListener(new AbstractAction() { 187 @Override 188 public void actionPerformed(ActionEvent e) { 189 OverpassQueryHistoryPopup.show(arrowButton, OverpassDownloadDialog.this); 190 } 191 }); 192 pane.add(scrollPane, BorderLayout.CENTER); 193 pane.add(arrowButton, BorderLayout.EAST); 194 pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5)); 195 GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); 196 gbc.ipady = 200; 197 pnl.add(pane, gbc); 198 199 overpassServer = new HistoryComboBox(); 200 overpassServer.getEditor().getEditorComponent().addFocusListener(disableActionsFocusListener); 201 pnl.add(new JLabel(tr("Overpass server: ")), GBC.std().insets(5, 5, 5, 5)); 202 pnl.add(overpassServer, GBC.eol().fill(GBC.HORIZONTAL)); 203 } 204 205 public String getOverpassServer() { 206 return overpassServer.getText(); 207 } 208 209 public String getOverpassQuery() { 210 return overpassQuery.getText(); 211 } 212 213 public void setOverpassQuery(String text) { 214 overpassQuery.setText(text); 215 } 216 217 @Override 218 public void restoreSettings() { 219 super.restoreSettings(); 220 overpassServer.setPossibleItems(OVERPASS_SERVER_HISTORY.get()); 221 overpassServer.setText(OVERPASS_SERVER.get()); 222 overpassWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get()); 223 } 224 225 @Override 226 public void rememberSettings() { 227 super.rememberSettings(); 228 overpassWizard.addCurrentItemToHistory(); 229 OVERPASS_SERVER.put(getOverpassServer()); 230 OVERPASS_SERVER_HISTORY.put(overpassServer.getHistory()); 231 OVERPASS_WIZARD_HISTORY.put(overpassWizard.getHistory()); 232 OverpassQueryHistoryPopup.addToHistory(getOverpassQuery()); 233 } 234 235 } 236 237 static class OverpassQueryHistoryPopup extends JPopupMenu { 238 239 static final CollectionProperty OVERPASS_QUERY_HISTORY = new CollectionProperty("download.overpass.query", new ArrayList<String>()); 240 static final IntegerProperty OVERPASS_QUERY_HISTORY_SIZE = new IntegerProperty("download.overpass.query.size", 12); 241 242 OverpassQueryHistoryPopup(final OverpassDownloadDialog dialog) { 243 final Collection<String> history = OVERPASS_QUERY_HISTORY.get(); 244 setLayout(new GridLayout((int) Math.ceil(history.size() / 2.), 2)); 245 for (final String i : history) { 246 add(new OverpassQueryHistoryItem(i, dialog)); 247 } 248 } 249 250 static void show(final JComponent parent, final OverpassDownloadDialog dialog) { 251 final OverpassQueryHistoryPopup menu = new OverpassQueryHistoryPopup(dialog); 252 final Rectangle r = parent.getBounds(); 253 menu.show(parent.getParent(), r.x + r.width - (int) menu.getPreferredSize().getWidth(), r.y + r.height); 254 } 255 256 static void addToHistory(final String query) { 257 final Deque<String> history = new LinkedList<>(OVERPASS_QUERY_HISTORY.get()); 258 if (!history.contains(query)) { 259 history.add(query); 260 } 261 while (history.size() > OVERPASS_QUERY_HISTORY_SIZE.get()) { 262 history.removeFirst(); 263 } 264 OVERPASS_QUERY_HISTORY.put(history); 265 } 266 } 267 268 static class OverpassQueryHistoryItem extends JMenuItem implements ActionListener { 269 270 final String query; 271 final OverpassDownloadDialog dialog; 272 273 OverpassQueryHistoryItem(final String query, final OverpassDownloadDialog dialog) { 274 this.query = query; 275 this.dialog = dialog; 276 setText("<html><pre style='width:300px;'>" + 277 Utils.escapeReservedCharactersHTML(Utils.restrictStringLines(query, 7))); 278 addActionListener(this); 279 } 280 281 @Override 282 public void actionPerformed(ActionEvent e) { 283 dialog.setOverpassQuery(query); 284 } 285 } 286 287}