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