Ticket #15057: overpass-dialog-refactored.patch
File overpass-dialog-refactored.patch, 60.0 KB (added by , 7 years ago) |
---|
-
src/org/openstreetmap/josm/actions/OverpassDownloadAction.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8
6 6 7 7 import java.awt.BorderLayout; 8 8 import java.awt.Component; 9 import java.awt. GridLayout;10 import java.awt. Rectangle;9 import java.awt.Dimension; 10 import java.awt.GridBagLayout; 11 11 import java.awt.event.ActionEvent; 12 import java.awt.event.ActionListener;13 12 import java.awt.event.FocusEvent; 14 13 import java.awt.event.FocusListener; 15 14 import java.awt.event.KeyEvent; 16 15 import java.util.ArrayList; 16 import java.util.Arrays; 17 17 import java.util.Collection; 18 18 import java.util.Collections; 19 import java.util.Deque; 20 import java.util.LinkedList; 19 import java.util.Optional; 21 20 import java.util.concurrent.Future; 21 import java.util.function.Consumer; 22 22 23 23 import javax.swing.AbstractAction; 24 24 import javax.swing.Action; 25 25 import javax.swing.ActionMap; 26 26 import javax.swing.JButton; 27 import javax.swing.J Component;27 import javax.swing.JEditorPane; 28 28 import javax.swing.JLabel; 29 import javax.swing.JMenuItem;30 29 import javax.swing.JOptionPane; 31 30 import javax.swing.JPanel; 32 import javax.swing.JPopupMenu;33 31 import javax.swing.JScrollPane; 32 import javax.swing.event.HyperlinkEvent; 34 33 import javax.swing.plaf.basic.BasicArrowButton; 34 import javax.swing.text.JTextComponent; 35 35 36 36 import org.openstreetmap.josm.Main; 37 37 import org.openstreetmap.josm.actions.downloadtasks.DownloadOsmTask; 38 38 import org.openstreetmap.josm.actions.downloadtasks.PostDownloadHandler; 39 39 import org.openstreetmap.josm.data.Bounds; 40 import org.openstreetmap.josm.data.preferences.BooleanProperty; 40 41 import org.openstreetmap.josm.data.preferences.CollectionProperty; 41 import org.openstreetmap.josm. data.preferences.IntegerProperty;42 import org.openstreetmap.josm.gui. HelpAwareOptionPane;42 import org.openstreetmap.josm.gui.ExtendedDialog; 43 import org.openstreetmap.josm.gui.OverpassQueryList; 43 44 import org.openstreetmap.josm.gui.download.DownloadDialog; 44 45 import org.openstreetmap.josm.gui.preferences.server.OverpassServerPreference; 45 46 import org.openstreetmap.josm.gui.util.GuiHelper; … … 47 48 import org.openstreetmap.josm.gui.widgets.JosmTextArea; 48 49 import org.openstreetmap.josm.io.OverpassDownloadReader; 49 50 import org.openstreetmap.josm.tools.GBC; 50 import org.openstreetmap.josm.tools. InputMapUtils;51 import org.openstreetmap.josm.tools.OpenBrowser; 51 52 import org.openstreetmap.josm.tools.OverpassTurboQueryWizard; 52 53 import org.openstreetmap.josm.tools.Shortcut; 53 54 import org.openstreetmap.josm.tools.UncheckedParseException; … … 76 77 OverpassDownloadDialog dialog = OverpassDownloadDialog.getInstance(); 77 78 dialog.restoreSettings(); 78 79 dialog.setVisible(true); 79 if (!dialog.isCanceled()) { 80 dialog.rememberSettings(); 81 Bounds area = dialog.getSelectedDownloadArea(); 82 DownloadOsmTask task = new DownloadOsmTask(); 83 task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired()); 84 Future<?> future = task.download( 85 new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), dialog.getOverpassQuery()), 86 dialog.isNewLayerRequired(), area, null); 87 Main.worker.submit(new PostDownloadHandler(task, future)); 88 } 80 81 if (dialog.isCanceled()) { 82 return; 83 } 84 85 dialog.rememberSettings(); 86 Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea(); 87 String overpassQuery = dialog.getOverpassQuery(); 88 89 /* 90 * Absence of the selected area can be justified only if the overpass query 91 * is not restricted to bbox. 92 */ 93 if (!selectedArea.isPresent() && overpassQuery.contains("{{bbox}}")) { 94 JOptionPane.showMessageDialog( 95 dialog, 96 tr("Please select a download area first."), 97 tr("Error"), 98 JOptionPane.ERROR_MESSAGE 99 ); 100 return; 101 } 102 103 /* 104 * A callback that is passed to PostDownloadReporter that is called once the download task 105 * has finished. According to the number of errors happened, their type we decide whether we 106 * want to save the last query in OverpassQueryList. 107 */ 108 Consumer<Collection> errorReporter = (errors) -> { 109 110 boolean onlyNoDataError = errors.size() == 1 && 111 errors.contains("No data found in this area."); 112 113 if (errors.isEmpty() || onlyNoDataError) { 114 dialog.saveHistoricItemOnSuccess(); 115 } 116 }; 117 118 /* 119 * In order to support queries generated by the Overpass Turbo Query Wizard tool 120 * which do not require the area to be specified. 121 */ 122 Bounds area = selectedArea.orElseGet(() -> new Bounds(0, 0, 0, 0)); 123 DownloadOsmTask task = new DownloadOsmTask(); 124 task.setZoomAfterDownload(dialog.isZoomToDownloadedDataRequired()); 125 Future<?> future = task.download( 126 new OverpassDownloadReader(area, OverpassServerPreference.getOverpassServer(), dialog.getOverpassQuery()), 127 dialog.isNewLayerRequired(), area, null); 128 Main.worker.submit(new PostDownloadHandler(task, future, errorReporter)); 89 129 } 90 130 91 131 private static final class DisableActionsFocusListener implements FocusListener { … … 121 161 122 162 private static final class OverpassDownloadDialog extends DownloadDialog { 123 163 124 private HistoryComboBox overpassWizard;125 164 private JosmTextArea overpassQuery; 165 private OverpassQueryList overpassQueryList; 126 166 private static OverpassDownloadDialog instance; 127 private static final CollectionProperty OVERPASS_WIZARD_HISTORY = new CollectionProperty("download.overpass.wizard",128 new ArrayList<String>());167 private static final BooleanProperty OVERPASS_QUERY_LIST_OPENED = 168 new BooleanProperty("download.overpass.query-list.opened", false); 129 169 130 170 private OverpassDownloadDialog(Component parent) { 131 171 super(parent, ht("/Action/OverpassDownload")); … … 145 185 146 186 @Override 147 187 protected void buildMainPanelAboveDownloadSelections(JPanel pnl) { 188 // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes 189 pnl.add(new JLabel(), GBC.eol()); 148 190 149 191 DisableActionsFocusListener disableActionsFocusListener = 150 192 new DisableActionsFocusListener(slippyMapChooser.getNavigationComponentActionMap()); 151 193 152 pnl.add(new JLabel(), GBC.eol()); // needed for the invisible checkboxes cbDownloadGpxData, cbDownloadNotes 153 154 final String tooltip = tr("Builds an Overpass query using the Overpass Turbo query wizard"); 155 overpassWizard = new HistoryComboBox(); 156 overpassWizard.setToolTipText(tooltip); 157 overpassWizard.getEditorComponent().addFocusListener(disableActionsFocusListener); 158 final JButton buildQuery = new JButton(tr("Build query")); 159 final Action buildQueryAction = new AbstractAction() { 194 String tooltip = tr("Build an Overpass query using the Overpass Turbo Query Wizard tool"); 195 Action queryWizardAction = new AbstractAction() { 160 196 @Override 161 197 public void actionPerformed(ActionEvent e) { 162 final String overpassWizardText = overpassWizard.getText(); 163 try { 164 overpassQuery.setText(OverpassTurboQueryWizard.getInstance().constructQuery(overpassWizardText)); 165 } catch (UncheckedParseException ex) { 166 Main.error(ex); 167 HelpAwareOptionPane.showOptionDialog( 168 Main.parent, 169 tr("<html>The Overpass wizard could not parse the following query:" 170 + Utils.joinAsHtmlUnorderedList(Collections.singleton(overpassWizardText))), 171 tr("Parse error"), 172 JOptionPane.ERROR_MESSAGE, 173 null 174 ); 175 } 198 QueryWizardDialog.getInstance().showDialog(); 176 199 } 177 200 }; 178 buildQuery.addActionListener(buildQueryAction);179 buildQuery.setToolTipText(tooltip);180 pnl.add(buildQuery, GBC.std().insets(5, 5, 5, 5));181 pnl.add(overpassWizard, GBC.eol().fill(GBC.HORIZONTAL));182 InputMapUtils.addEnterAction(overpassWizard.getEditorComponent(), buildQueryAction);183 201 184 overpassQuery = new JosmTextArea("", 8, 80); 185 overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery)); 186 overpassQuery.addFocusListener(disableActionsFocusListener); 187 JScrollPane scrollPane = new JScrollPane(overpassQuery); 188 final JPanel pane = new JPanel(new BorderLayout()); 189 final BasicArrowButton arrowButton = new BasicArrowButton(BasicArrowButton.SOUTH); 190 arrowButton.addActionListener(new AbstractAction() { 202 JButton openQueryWizard = new JButton("Query Wizard"); 203 openQueryWizard.setToolTipText(tooltip); 204 openQueryWizard.addActionListener(queryWizardAction); 205 206 // CHECKSTYLE.OFF: LineLength 207 this.overpassQuery = new JosmTextArea( 208 "/*\n" + 209 tr("Place your Overpass query below or generate one using the Overpass Turbo Query Wizard") 210 + "\n*/", 211 8, 80); 212 // CHECKSTYLE.ON: LineLength 213 this.overpassQuery.setFont(GuiHelper.getMonospacedFont(overpassQuery)); 214 this.overpassQuery.addFocusListener(disableActionsFocusListener); 215 this.overpassQuery.addFocusListener(new FocusListener() { 191 216 @Override 192 public void actionPerformed(ActionEvent e) { 193 OverpassQueryHistoryPopup.show(arrowButton, OverpassDownloadDialog.this); 217 public void focusGained(FocusEvent e) { 218 overpassQuery.selectAll(); 219 } 220 221 @Override 222 public void focusLost(FocusEvent e) { 223 194 224 } 195 225 }); 196 pane.add(scrollPane, BorderLayout.CENTER); 197 pane.add(arrowButton, BorderLayout.EAST); 198 pnl.add(new JLabel(tr("Overpass query: ")), GBC.std().insets(5, 5, 5, 5)); 199 GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); 200 gbc.ipady = 200; 226 227 this.overpassQueryList = new OverpassQueryList(this, this.overpassQuery); 228 overpassQueryList.setToolTipText(tr("Show/hide Overpass snippet list")); 229 overpassQueryList.setVisible(OVERPASS_QUERY_LIST_OPENED.get()); 230 overpassQueryList.setPreferredSize(new Dimension(350, 300)); 231 JScrollPane scrollPane = new JScrollPane(overpassQuery); 232 BasicArrowButton arrowButton = new BasicArrowButton(overpassQueryList.isVisible() 233 ? BasicArrowButton.EAST 234 : BasicArrowButton.WEST); 235 arrowButton.addActionListener(e -> { 236 if (overpassQueryList.isVisible()) { 237 overpassQueryList.setVisible(false); 238 arrowButton.setDirection(BasicArrowButton.WEST); 239 OVERPASS_QUERY_LIST_OPENED.put(false); 240 } else { 241 overpassQueryList.setVisible(true); 242 arrowButton.setDirection(BasicArrowButton.EAST); 243 OVERPASS_QUERY_LIST_OPENED.put(false); 244 } 245 }); 246 247 JPanel innerPanel = new JPanel(new BorderLayout()); 248 innerPanel.add(scrollPane, BorderLayout.CENTER); 249 innerPanel.add(arrowButton, BorderLayout.EAST); 250 251 JPanel pane = new JPanel(new BorderLayout()); 252 pane.add(innerPanel, BorderLayout.CENTER); 253 pane.add(overpassQueryList, BorderLayout.EAST); 254 255 GBC gbc = GBC.eol().fill(GBC.HORIZONTAL); gbc.ipady = 200; 256 pnl.add(openQueryWizard, GBC.std().insets(5, 5, 5, 5)); 201 257 pnl.add(pane, gbc); 202 203 258 } 204 259 205 publicString getOverpassQuery() {260 String getOverpassQuery() { 206 261 return overpassQuery.getText(); 207 262 } 208 263 209 publicvoid setOverpassQuery(String text) {264 void setOverpassQuery(String text) { 210 265 overpassQuery.setText(text); 211 266 } 212 267 213 @Override 214 public void restoreSettings() { 215 super.restoreSettings(); 216 overpassWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get()); 217 } 218 219 @Override 220 public void rememberSettings() { 221 super.rememberSettings(); 222 overpassWizard.addCurrentItemToHistory(); 223 OVERPASS_WIZARD_HISTORY.put(overpassWizard.getHistory()); 224 OverpassQueryHistoryPopup.addToHistory(getOverpassQuery()); 268 /** 269 * Adds the current query to {@link OverpassQueryList}. 270 */ 271 void saveHistoricItemOnSuccess() { 272 overpassQueryList.saveHistoricItem(overpassQuery.getText()); 225 273 } 226 274 227 275 @Override 228 276 protected void updateSizeCheck() { 229 277 displaySizeCheckResult(false); 230 278 } 279 280 /** 281 * Triggers the download action to fire. 282 */ 283 private void triggerDownload() { 284 super.btnDownload.doClick(); 285 } 231 286 } 232 287 233 static class OverpassQueryHistoryPopup extends JPopupMenu{288 private static final class QueryWizardDialog extends ExtendedDialog { 234 289 235 static final CollectionProperty OVERPASS_QUERY_HISTORY = new CollectionProperty("download.overpass.query", new ArrayList<String>()); 236 static final IntegerProperty OVERPASS_QUERY_HISTORY_SIZE = new IntegerProperty("download.overpass.query.size", 12); 290 private static QueryWizardDialog dialog; 291 private final HistoryComboBox queryWizard; 292 private final OverpassTurboQueryWizard overpassQueryBuilder; 293 private static final CollectionProperty OVERPASS_WIZARD_HISTORY = 294 new CollectionProperty("download.overpass.wizard", new ArrayList<String>()); 237 295 238 OverpassQueryHistoryPopup(final OverpassDownloadDialog dialog) { 239 final Collection<String> history = OVERPASS_QUERY_HISTORY.get(); 240 setLayout(new GridLayout((int) Math.ceil(history.size() / 2.), 2)); 241 for (final String i : history) { 242 add(new OverpassQueryHistoryItem(i, dialog)); 243 } 244 } 296 // dialog buttons 297 private static final int BUILD_QUERY = 0; 298 private static final int BUILD_AN_EXECUTE_QUERY = 1; 299 private static final int CANCEL = 2; 245 300 246 static void show(final JComponent parent, final OverpassDownloadDialog dialog) { 247 final OverpassQueryHistoryPopup menu = new OverpassQueryHistoryPopup(dialog); 248 final Rectangle r = parent.getBounds(); 249 menu.show(parent.getParent(), r.x + r.width - (int) menu.getPreferredSize().getWidth(), r.y + r.height); 250 } 301 /** 302 * Get an instance of {@link QueryWizardDialog}. 303 */ 304 public static QueryWizardDialog getInstance() { 305 if (dialog == null) { 306 dialog = new QueryWizardDialog(); 307 } 251 308 252 static void addToHistory(final String query) { 253 final Deque<String> history = new LinkedList<>(OVERPASS_QUERY_HISTORY.get()); 254 if (!history.contains(query)) { 255 history.add(query); 256 } 257 while (history.size() > OVERPASS_QUERY_HISTORY_SIZE.get()) { 258 history.removeFirst(); 259 } 260 OVERPASS_QUERY_HISTORY.put(history); 309 return dialog; 261 310 } 262 }263 311 264 static class OverpassQueryHistoryItem extends JMenuItem implements ActionListener { 312 private static final String DESCRIPTION_STYLE = 313 "<style type=\"text/css\">\n" 314 + "table { border-spacing: 0pt;}\n" 315 + "h3 {text-align: center; padding: 8px;}\n" 316 + "td {border: 1px solid #dddddd; text-align: left; padding: 8px;}\n" 317 + "#desc {width: 350px;}" 318 + "</style>\n"; 265 319 266 final String query; 267 final OverpassDownloadDialog dialog; 320 private QueryWizardDialog() { 321 super(OverpassDownloadDialog.getInstance(), tr("Overpass Turbo Query Wizard"), 322 tr("Build query"), tr("Build query and execute"), tr("Cancel")); 268 323 269 OverpassQueryHistoryItem(final String query, final OverpassDownloadDialog dialog) { 270 this.query = query; 271 this.dialog = dialog; 272 setText("<html><pre style='width:300px;'>" + 273 Utils.escapeReservedCharactersHTML(Utils.restrictStringLines(query, 7))); 274 addActionListener(this); 324 this.queryWizard = new HistoryComboBox(); 325 this.overpassQueryBuilder = OverpassTurboQueryWizard.getInstance(); 326 327 JPanel panel = new JPanel(new GridBagLayout()); 328 329 JLabel searchLabel = new JLabel(tr("Search :")); 330 JTextComponent descPane = this.buildDescriptionSection(); 331 JScrollPane scroll = GuiHelper.embedInVerticalScrollPane(descPane); 332 scroll.getVerticalScrollBar().setUnitIncrement(10); // make scrolling smooth 333 334 panel.add(searchLabel, GBC.std().insets(0, 0, 0, 20).anchor(GBC.SOUTHEAST)); 335 panel.add(queryWizard, GBC.eol().insets(0, 0, 0, 15).fill(GBC.HORIZONTAL).anchor(GBC.SOUTH)); 336 panel.add(scroll, GBC.eol().fill(GBC.BOTH).anchor(GBC.CENTER)); 337 338 queryWizard.setPossibleItems(OVERPASS_WIZARD_HISTORY.get()); 339 340 setCancelButton(CANCEL); 341 setDefaultButton(BUILD_AN_EXECUTE_QUERY + 1); // Build and execute button 342 setContent(panel, false); 275 343 } 276 344 277 345 @Override 278 public void actionPerformed(ActionEvent e) { 279 dialog.setOverpassQuery(query); 280 } 281 } 346 public void buttonAction(int buttonIndex, ActionEvent evt) { 347 switch (buttonIndex) { 348 case BUILD_QUERY: 349 if (this.buildQueryAction()) { 350 this.saveHistory(); 351 super.buttonAction(BUILD_QUERY, evt); 352 } 353 break; 354 case BUILD_AN_EXECUTE_QUERY: 355 if (this.buildQueryAction()) { 356 this.saveHistory(); 357 super.buttonAction(BUILD_AN_EXECUTE_QUERY, evt); 358 359 OverpassDownloadDialog.getInstance().triggerDownload(); 360 } 361 break; 362 default: 363 super.buttonAction(buttonIndex, evt); 282 364 283 } 365 } 366 } 367 368 /** 369 * Saves the latest, successfully parsed search term. 370 */ 371 private void saveHistory() { 372 queryWizard.addCurrentItemToHistory(); 373 OVERPASS_WIZARD_HISTORY.put(queryWizard.getHistory()); 374 } 375 376 /** 377 * Tries to process a search term using {@link OverpassTurboQueryWizard}. If the term cannot 378 * be parsed, the the corresponding dialog is shown. 379 * @param searchTerm The search term to parse. 380 * @return {@link Optional#empty()} if an exception was thrown when parsing, meaning 381 * that the term cannot be processed, or non-empty {@link Optional} containing the result 382 * of parsing. 383 */ 384 private Optional<String> tryParseSearchTerm(String searchTerm) { 385 try { 386 String query = this.overpassQueryBuilder.constructQuery(searchTerm); 387 388 return Optional.of(query); 389 } catch (UncheckedParseException ex) { 390 Main.error(ex); 391 JOptionPane.showMessageDialog( 392 OverpassDownloadDialog.getInstance(), 393 "<html>" + 394 tr("The Overpass wizard could not parse the following query:") + 395 Utils.joinAsHtmlUnorderedList(Collections.singleton(searchTerm)) + 396 "</html>", 397 tr("Parse error"), 398 JOptionPane.ERROR_MESSAGE 399 ); 400 401 return Optional.empty(); 402 } 403 } 404 405 /** 406 * Builds an Overpass query out from {@link QueryWizardDialog#queryWizard} contents. 407 * @return {@code true} if the query successfully built, {@code false} otherwise. 408 */ 409 private boolean buildQueryAction() { 410 final String wizardSearchTerm = this.queryWizard.getText(); 411 412 Optional<String> q = this.tryParseSearchTerm(wizardSearchTerm); 413 if (q.isPresent()) { 414 String query = q.get(); 415 OverpassDownloadDialog.getInstance().setOverpassQuery(query); 416 417 return true; 418 } 419 420 return false; 421 } 422 423 private JTextComponent buildDescriptionSection() { 424 JEditorPane descriptionSection = new JEditorPane("text/html", this.getDescriptionContent()); 425 descriptionSection.setEditable(false); 426 descriptionSection.addHyperlinkListener(e -> { 427 if (HyperlinkEvent.EventType.ACTIVATED.equals(e.getEventType())) { 428 OpenBrowser.displayUrl(e.getURL().toString()); 429 } 430 }); 431 432 return descriptionSection; 433 } 434 435 private String getDescriptionContent() { 436 return new StringBuilder("<html>") 437 .append(DESCRIPTION_STYLE) 438 .append("<body>") 439 .append("<h3>") 440 .append(tr("Query Wizard")) 441 .append("</h3>") 442 .append("<p>") 443 .append(tr("Allows you to interact with <i>Overpass API</i> by writing declarative, human-readable terms.")) 444 .append(tr("The <i>Query Wizard</i> tool will transform those to a valid overpass query.")) 445 .append(tr("For more detailed description see ")) 446 .append(tr("<a href=\"{0}\">OSM Wiki</a>.", Main.getOSMWebsite() + "/wiki/Overpass_turbo/Wizard")) 447 .append("</p>") 448 .append("<h3>").append(tr("Hints")).append("</h3>") 449 .append("<table>").append("<tr>").append("<td>") 450 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>type:node</i>", "<i>type:relation</i>", "<i>type:way</i>"))) 451 .append("</td>").append("<td>") 452 .append("<span>").append(tr("Download objects of a certain type.")).append("</span>") 453 .append("</td>").append("</tr>") 454 .append("<tr>").append("<td>") 455 .append(Utils.joinAsHtmlUnorderedList( 456 Arrays.asList("<i>key=value in <u>location</u></i>", 457 "<i>key=value around <u>location</u></i>", 458 "<i>key=value in bbox</i>"))) 459 .append("</td>").append("<td>") 460 .append(tr("Download object by specifying a specific location. For example,")) 461 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList( 462 tr("{0} all objects having {1} as attribute are downloaded.", "<i>tourism=hotel in Berlin</i> -", "'tourism=hotel'"), 463 tr("{0} all object with the corresponding key/value pair located around Berlin. Note, the default value for radius "+ 464 "is set to 1000m, but it can be changed in the generated query.", "<i>tourism=hotel around Berlin</i> -"), 465 tr("{0} all objects within the current selection that have {1} as attribute.", "<i>tourism=hotel in bbox</i> -", 466 "'tourism=hotel'")))) 467 .append("<span>") 468 .append(tr("Instead of <i>location</i> any valid place name can be used like address, city, etc.")) 469 .append("</span>") 470 .append("</td>").append("</tr>") 471 .append("<tr>").append("<td>") 472 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList("<i>key=value</i>", "<i>key=*</i>", "<i>key~regex</i>", 473 "<i>key!=value</i>", "<i>key!~regex</i>", "<i>key=\"combined value\"</i>"))) 474 .append("</td>").append("<td>") 475 .append(tr("<span>Download objects that have some concrete key/value pair, only the key with any contents for the value, " + 476 "the value matching some regular expression. 'Not equal' operators are supported as well.</span>")) 477 .append("</td>").append("</tr>") 478 .append("<tr>").append("<td>") 479 .append(Utils.joinAsHtmlUnorderedList(Arrays.asList( 480 tr("<i>expression1 {0} expression2</i>", "or"), 481 tr("<i>expression1 {0} expression2</i>", "and")))) 482 .append("</td>").append("<td>") 483 .append("<span>") 484 .append(tr("Basic logical operators can be used to create more sophisticated queries. Instead of 'or' - '|', '||' " + 485 "can be used, and instead of 'and' - '&', '&&'.")) 486 .append("</span>") 487 .append("</td>").append("</tr>").append("</table>") 488 .append("</body>") 489 .append("</html>") 490 .toString(); 491 } 492 } 493 } 494 No newline at end of file -
src/org/openstreetmap/josm/actions/downloadtasks/PostDownloadHandler.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8
11 11 import java.util.concurrent.CancellationException; 12 12 import java.util.concurrent.ExecutionException; 13 13 import java.util.concurrent.Future; 14 import java.util.function.Consumer; 14 15 15 16 import javax.swing.JOptionPane; 16 17 import javax.swing.SwingUtilities; … … 28 29 public class PostDownloadHandler implements Runnable { 29 30 private final DownloadTask task; 30 31 private final Future<?> future; 32 private Consumer<Collection> errorReporter; 31 33 32 34 /** 33 35 * constructor … … 39 41 this.future = future; 40 42 } 41 43 44 /** 45 * constructor 46 * @param task the asynchronous download task 47 * @param future the future on which the completion of the download task can be synchronized 48 * @param errorReporter a callback to inform about the number errors happened during the download 49 * task 50 */ 51 public PostDownloadHandler(DownloadTask task, Future<?> future, Consumer<Collection> errorReporter) { 52 this(task, future); 53 this.errorReporter = errorReporter; 54 } 55 42 56 @Override 43 57 public void run() { 44 58 // wait for downloads task to finish (by waiting for the future to return a value) … … 53 67 // make sure errors are reported only once 54 68 // 55 69 Set<Object> errors = new LinkedHashSet<>(task.getErrorObjects()); 56 if (errors.isEmpty()) 70 if (this.errorReporter != null) { 71 errorReporter.accept(errors); 72 } 73 74 if (errors.isEmpty()) { 57 75 return; 76 } 58 77 59 78 // just one error object? 60 79 // … … 100 119 return; 101 120 } 102 121 } 103 } 122 } 123 No newline at end of file -
src/org/openstreetmap/josm/gui/download/DownloadDialog.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8
19 19 import java.awt.event.WindowEvent; 20 20 import java.util.ArrayList; 21 21 import java.util.List; 22 import java.util.Optional; 22 23 23 24 import javax.swing.AbstractAction; 24 25 import javax.swing.JButton; … … 457 458 } 458 459 459 460 /** 460 * Re pliesthe currently selected download area.461 * @return the currently selected download area. May be {@code null}, if no download area is selected yet.461 * Returns an {@link Optional} of the currently selected download area. 462 * @return An {@link Optional} of the currently selected download area. 462 463 */ 463 public BoundsgetSelectedDownloadArea() {464 return currentBounds;464 public Optional<Bounds> getSelectedDownloadArea() { 465 return Optional.ofNullable(currentBounds); 465 466 } 466 467 467 468 @Override … … 524 525 } 525 526 526 527 public void run() { 527 if (currentBounds == null) { 528 JOptionPane.showMessageDialog( 529 DownloadDialog.this, 530 tr("Please select a download area first."), 531 tr("Error"), 532 JOptionPane.ERROR_MESSAGE 533 ); 534 return; 535 } 528 /* 529 * Checks if the user selected the type of data to download. At least one the following 530 * must be chosen : raw osm data, gpx data, notes. 531 * If none of those are selected, then the corresponding dialog is shown to inform the user. 532 */ 536 533 if (!isDownloadOsmData() && !isDownloadGpxData() && !isDownloadNotes()) { 537 534 JOptionPane.showMessageDialog( 538 535 DownloadDialog.this, 539 536 tr("<html>Neither <strong>{0}</strong> nor <strong>{1}</strong> nor <strong>{2}</strong> is enabled.<br>" 540 + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>",537 + "Please choose to either download OSM data, or GPX data, or Notes, or all.</html>", 541 538 cbDownloadOsmData.getText(), 542 539 cbDownloadGpxData.getText(), 543 540 cbDownloadNotes.getText() … … 547 544 ); 548 545 return; 549 546 } 547 550 548 setCanceled(false); 551 549 setVisible(false); 552 550 } … … 568 566 btnDownload.requestFocusInWindow(); 569 567 } 570 568 } 571 } 569 } 570 No newline at end of file -
src/org/openstreetmap/josm/actions/DownloadAction.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8
8 8 import java.awt.event.KeyEvent; 9 9 import java.util.ArrayList; 10 10 import java.util.List; 11 import java.util.Optional; 11 12 import java.util.concurrent.ExecutionException; 12 13 import java.util.concurrent.Future; 13 14 … … 25 26 import org.openstreetmap.josm.tools.Pair; 26 27 import org.openstreetmap.josm.tools.Shortcut; 27 28 29 import javax.swing.JOptionPane; 30 28 31 /** 29 32 * Action that opens a connection to the osm server and downloads map data. 30 33 * … … 50 53 DownloadDialog dialog = DownloadDialog.getInstance(); 51 54 dialog.restoreSettings(); 52 55 dialog.setVisible(true); 53 if (!dialog.isCanceled()) { 54 dialog.rememberSettings(); 55 final Bounds area = dialog.getSelectedDownloadArea(); 56 final boolean zoom = dialog.isZoomToDownloadedDataRequired(); 57 final List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>(); 58 if (dialog.isDownloadOsmData()) { 59 DownloadOsmTask task = new DownloadOsmTask(); 60 task.setZoomAfterDownload(zoom && !dialog.isDownloadGpxData() && !dialog.isDownloadNotes()); 61 Future<?> future = task.download(dialog.isNewLayerRequired(), area, null); 62 Main.worker.submit(new PostDownloadHandler(task, future)); 63 if (zoom) { 64 tasks.add(new Pair<>(task, future)); 65 } 66 } 67 if (dialog.isDownloadGpxData()) { 68 DownloadGpsTask task = new DownloadGpsTask(); 69 task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadNotes()); 70 Future<?> future = task.download(dialog.isNewLayerRequired(), area, null); 71 Main.worker.submit(new PostDownloadHandler(task, future)); 72 if (zoom) { 73 tasks.add(new Pair<>(task, future)); 74 } 75 } 76 if (dialog.isDownloadNotes()) { 77 DownloadNotesTask task = new DownloadNotesTask(); 78 task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadGpxData()); 79 Future<?> future = task.download(false, area, null); 80 Main.worker.submit(new PostDownloadHandler(task, future)); 81 if (zoom) { 82 tasks.add(new Pair<>(task, future)); 83 } 84 } 85 if (zoom && tasks.size() > 1) { 86 Main.worker.submit(() -> { 87 ProjectionBounds bounds = null; 88 // Wait for completion of download jobs 89 for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) { 90 try { 91 p.b.get(); 92 ProjectionBounds b = p.a.getDownloadProjectionBounds(); 93 if (bounds == null) { 94 bounds = b; 95 } else if (b != null) { 96 bounds.extend(b); 97 } 98 } catch (InterruptedException | ExecutionException ex) { 99 Main.warn(ex); 100 } 101 } 102 // Zoom to the larger download bounds 103 if (Main.map != null && bounds != null) { 104 final ProjectionBounds pb = bounds; 105 GuiHelper.runInEDTAndWait(() -> Main.map.mapView.zoomTo(new ViewportData(pb))); 106 } 107 }); 108 } 56 57 if (dialog.isCanceled()) { 58 return; 59 } 60 61 dialog.rememberSettings(); 62 63 Optional<Bounds> selectedArea = dialog.getSelectedDownloadArea(); 64 if (!selectedArea.isPresent()) { 65 JOptionPane.showMessageDialog( 66 dialog, 67 tr("Please select a download area first."), 68 tr("Error"), 69 JOptionPane.ERROR_MESSAGE 70 ); 71 return; 72 } 73 74 final Bounds area = selectedArea.get(); 75 final boolean zoom = dialog.isZoomToDownloadedDataRequired(); 76 final List<Pair<AbstractDownloadTask<?>, Future<?>>> tasks = new ArrayList<>(); 77 78 if (dialog.isDownloadOsmData()) { 79 DownloadOsmTask task = new DownloadOsmTask(); 80 task.setZoomAfterDownload(zoom && !dialog.isDownloadGpxData() && !dialog.isDownloadNotes()); 81 Future<?> future = task.download(dialog.isNewLayerRequired(), area, null); 82 Main.worker.submit(new PostDownloadHandler(task, future)); 83 if (zoom) { 84 tasks.add(new Pair<>(task, future)); 85 } 86 } 87 88 if (dialog.isDownloadGpxData()) { 89 DownloadGpsTask task = new DownloadGpsTask(); 90 task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadNotes()); 91 Future<?> future = task.download(dialog.isNewLayerRequired(), area, null); 92 Main.worker.submit(new PostDownloadHandler(task, future)); 93 if (zoom) { 94 tasks.add(new Pair<>(task, future)); 95 } 96 } 97 98 if (dialog.isDownloadNotes()) { 99 DownloadNotesTask task = new DownloadNotesTask(); 100 task.setZoomAfterDownload(zoom && !dialog.isDownloadOsmData() && !dialog.isDownloadGpxData()); 101 Future<?> future = task.download(false, area, null); 102 Main.worker.submit(new PostDownloadHandler(task, future)); 103 if (zoom) { 104 tasks.add(new Pair<>(task, future)); 105 } 106 } 107 108 if (zoom && tasks.size() > 1) { 109 Main.worker.submit(() -> { 110 ProjectionBounds bounds = null; 111 // Wait for completion of download jobs 112 for (Pair<AbstractDownloadTask<?>, Future<?>> p : tasks) { 113 try { 114 p.b.get(); 115 ProjectionBounds b = p.a.getDownloadProjectionBounds(); 116 if (bounds == null) { 117 bounds = b; 118 } else if (b != null) { 119 bounds.extend(b); 120 } 121 } catch (InterruptedException | ExecutionException ex) { 122 Main.warn(ex); 123 } 124 } 125 // Zoom to the larger download bounds 126 if (Main.map != null && bounds != null) { 127 final ProjectionBounds pb = bounds; 128 GuiHelper.runInEDTAndWait(() -> Main.map.mapView.zoomTo(new ViewportData(pb))); 129 } 130 }); 109 131 } 110 132 } 111 } 133 } 134 No newline at end of file -
src/org/openstreetmap/josm/gui/OverpassQueryList.java
IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.gui; 3 4 import org.openstreetmap.josm.Main; 5 import org.openstreetmap.josm.gui.util.GuiHelper; 6 import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 7 import org.openstreetmap.josm.gui.widgets.DefaultTextComponentValidator; 8 import org.openstreetmap.josm.gui.widgets.JosmTextArea; 9 import org.openstreetmap.josm.gui.widgets.SearchTextResultListPanel; 10 import org.openstreetmap.josm.tools.GBC; 11 import org.openstreetmap.josm.tools.Utils; 12 13 import javax.swing.AbstractAction; 14 import javax.swing.BorderFactory; 15 import javax.swing.JLabel; 16 import javax.swing.JList; 17 import javax.swing.JOptionPane; 18 import javax.swing.JPanel; 19 import javax.swing.JPopupMenu; 20 import javax.swing.JScrollPane; 21 import javax.swing.JTextField; 22 import javax.swing.ListCellRenderer; 23 import javax.swing.SwingUtilities; 24 import javax.swing.border.CompoundBorder; 25 import javax.swing.text.JTextComponent; 26 import java.awt.Color; 27 import java.awt.Component; 28 import java.awt.Dimension; 29 import java.awt.Font; 30 import java.awt.GridBagLayout; 31 import java.awt.Point; 32 import java.awt.event.ActionEvent; 33 import java.awt.event.ActionListener; 34 import java.awt.event.MouseAdapter; 35 import java.awt.event.MouseEvent; 36 import java.time.LocalDateTime; 37 import java.time.format.DateTimeFormatter; 38 import java.util.ArrayList; 39 import java.util.Collection; 40 import java.util.Collections; 41 import java.util.HashMap; 42 import java.util.Locale; 43 import java.util.Map; 44 import java.util.Objects; 45 import java.util.Optional; 46 import java.util.stream.Collectors; 47 48 import static org.openstreetmap.josm.tools.I18n.tr; 49 50 /** 51 * A component to select user saved Overpass queries. 52 */ 53 public final class OverpassQueryList extends SearchTextResultListPanel<OverpassQueryList.SelectorItem> { 54 55 private final DateTimeFormatter format = DateTimeFormatter.ofPattern("HH:mm:ss, dd-MM-yyyy"); 56 57 /* 58 * GUI elements 59 */ 60 private final JTextComponent target; 61 private final Component componentParent; 62 63 /* 64 * All loaded elements within the list. 65 */ 66 private final transient Map<String, SelectorItem> items; 67 68 /* 69 * Preferences 70 */ 71 private static final String KEY_KEY = "key"; 72 private static final String QUERY_KEY = "query"; 73 private static final String USE_COUNT_KEY = "useCount"; 74 private static final String PREFERENCE_ITEMS = "download.overpass.query"; 75 76 /** 77 * Constructs a new {@code OverpassQueryList}. 78 * @param parent The parent of this component. 79 * @param target The text component to which the queries must be added. 80 */ 81 public OverpassQueryList(Component parent, JTextComponent target) { 82 this.target = target; 83 this.componentParent = parent; 84 this.items = this.restorePreferences(); 85 86 OverpassQueryListMouseAdapter mouseHandler = new OverpassQueryListMouseAdapter(lsResult, lsResultModel); 87 super.lsResult.setCellRenderer(new OverpassQueryCellRendered()); 88 super.setDblClickListener(this::getDblClickListener); 89 super.lsResult.addMouseListener(mouseHandler); 90 super.lsResult.addMouseMotionListener(mouseHandler); 91 92 filterItems(); 93 } 94 95 /** 96 * Returns currently selected element from the list. 97 * @return An {@link Optional#empty()} if nothing is selected, otherwise 98 * the idem is returned. 99 */ 100 public synchronized Optional<SelectorItem> getSelectedItem() { 101 int idx = lsResult.getSelectedIndex(); 102 if (lsResultModel.getSize() == 0 || idx == -1) { 103 return Optional.empty(); 104 } 105 106 SelectorItem item = lsResultModel.getElementAt(idx); 107 item.increaseUsageCount(); 108 109 this.items.values().stream() 110 .filter(it -> !it.getKey().equals(item.getKey())) 111 .forEach(SelectorItem::decreaseUsageCount); 112 113 filterItems(); 114 115 return Optional.of(item); 116 } 117 118 /** 119 * Adds a new historic item to the list. The key has form 'history {current date}'. 120 * Note, the item is not saved if there is already a historic item with the same query. 121 * @param query The query of the item. 122 * @exception IllegalArgumentException if the query is empty. 123 * @exception NullPointerException if the query is {@code null}. 124 */ 125 public synchronized void saveHistoricItem(String query) { 126 boolean historicExist = this.items.values().stream() 127 .filter(it -> it.getKey().contains("history")) 128 .map(SelectorItem::getQuery) 129 .anyMatch(q -> q.equals(query)); 130 131 if (!historicExist) { 132 SelectorItem item = new SelectorItem( 133 "history " + LocalDateTime.now().format(this.format), 134 query); 135 136 this.items.put(item.getKey(), item); 137 138 savePreferences(); 139 filterItems(); 140 } 141 } 142 143 /** 144 * Removes currently selected item, saves the current state to preferences and 145 * updates the view. 146 */ 147 private synchronized void removeSelectedItem() { 148 Optional<SelectorItem> it = this.getSelectedItem(); 149 150 if (!it.isPresent()) { 151 JOptionPane.showMessageDialog( 152 componentParent, 153 tr("Please select an item first")); 154 return; 155 } 156 157 SelectorItem item = it.get(); 158 if (this.items.remove(item.getKey(), item)) { 159 savePreferences(); 160 filterItems(); 161 } 162 } 163 164 /** 165 * Opens {@link EditItemDialog} for the selected item, saves the current state 166 * to preferences and updates the view. 167 */ 168 private synchronized void editSelectedItem() { 169 Optional<SelectorItem> it = this.getSelectedItem(); 170 171 if (!it.isPresent()) { 172 JOptionPane.showMessageDialog( 173 componentParent, 174 tr("Please select an item first")); 175 return; 176 } 177 178 SelectorItem item = it.get(); 179 180 EditItemDialog dialog = new EditItemDialog( 181 componentParent, 182 tr("Edit item"), 183 item.getKey(), 184 item.getQuery(), 185 new String[] {tr("Save")}); 186 dialog.showDialog(); 187 188 Optional<SelectorItem> editedItem = dialog.getOutputItem(); 189 editedItem.ifPresent(i -> { 190 this.items.remove(item.getKey(), item); 191 this.items.put(i.getKey(), i); 192 193 savePreferences(); 194 filterItems(); 195 }); 196 } 197 198 /** 199 * Opens {@link EditItemDialog}, saves the state to preferences if a new item is added 200 * and updates the view. 201 */ 202 private synchronized void createNewItem() { 203 EditItemDialog dialog = new EditItemDialog(componentParent, tr("Add snippet"), tr("Add")); 204 dialog.showDialog(); 205 206 Optional<SelectorItem> newItem = dialog.getOutputItem(); 207 newItem.ifPresent(i -> { 208 items.put(i.getKey(), new SelectorItem(i.getKey(), i.getQuery())); 209 savePreferences(); 210 filterItems(); 211 }); 212 } 213 214 @Override 215 public void setDblClickListener(ActionListener dblClickListener) { 216 // this listener is already set within this class 217 } 218 219 @Override 220 protected void filterItems() { 221 String text = edSearchText.getText().toLowerCase(Locale.ENGLISH); 222 223 super.lsResultModel.setItems(this.items.values().stream() 224 .filter(item -> item.getKey().contains(text)) 225 .collect(Collectors.toList())); 226 } 227 228 private void getDblClickListener(ActionEvent e) { 229 Optional<SelectorItem> selectedItem = this.getSelectedItem(); 230 231 if (!selectedItem.isPresent()) { 232 return; 233 } 234 235 SelectorItem item = selectedItem.get(); 236 this.target.setText(item.getQuery()); 237 } 238 239 /** 240 * Saves all elements from the list to {@link Main#pref}. 241 */ 242 private void savePreferences() { 243 Collection<Map<String, String>> toSave = new ArrayList<>(this.items.size()); 244 for (SelectorItem item : this.items.values()) { 245 Map<String, String> it = new HashMap<>(); 246 it.put(KEY_KEY, item.getKey()); 247 it.put(QUERY_KEY, item.getQuery()); 248 it.put(USE_COUNT_KEY, Integer.toString(item.getUsageCount())); 249 250 toSave.add(it); 251 } 252 253 Main.pref.putListOfStructs(PREFERENCE_ITEMS, toSave); 254 } 255 256 /** 257 * Loads the user saved items from {@link Main#pref}. 258 * @return A set of the user saved items. 259 */ 260 private Map<String, SelectorItem> restorePreferences() { 261 Collection<Map<String, String>> toRetrieve = 262 Main.pref.getListOfStructs(PREFERENCE_ITEMS, Collections.emptyList()); 263 Map<String, SelectorItem> result = new HashMap<>(); 264 265 for (Map<String, String> entry : toRetrieve) { 266 String key = entry.get(KEY_KEY); 267 String query = entry.get(QUERY_KEY); 268 int usageCount = Integer.parseInt(entry.get(USE_COUNT_KEY)); 269 270 result.put(key, new SelectorItem(key, query, usageCount)); 271 } 272 273 return result; 274 } 275 276 private class OverpassQueryListMouseAdapter extends MouseAdapter { 277 278 private final JList list; 279 private final ResultListModel model; 280 private final JPopupMenu emptySelectionPopup = new JPopupMenu(); 281 private final JPopupMenu elementPopup = new JPopupMenu(); 282 private final JPopupMenu queryLookup = new JPopupMenu(); 283 284 OverpassQueryListMouseAdapter(JList list, ResultListModel listModel) { 285 this.list = list; 286 this.model = listModel; 287 288 this.initPopupMenus(); 289 } 290 291 /* 292 * Do not select the closest element if the user clicked on 293 * an empty area within the list. 294 */ 295 private int locationToIndex(Point p) { 296 int idx = list.locationToIndex(p); 297 298 if (idx != -1 && !list.getCellBounds(idx, idx).contains(p)) { 299 return -1; 300 } else { 301 return idx; 302 } 303 } 304 305 @Override 306 public void mouseClicked(MouseEvent e) { 307 super.mouseClicked(e); 308 if (SwingUtilities.isRightMouseButton(e)) { 309 int index = locationToIndex(e.getPoint()); 310 311 if (model.getSize() == 0 || index == -1) { 312 list.clearSelection(); 313 emptySelectionPopup.show(list, e.getX(), e.getY()); 314 } else { 315 list.setSelectedIndex(index); 316 list.ensureIndexIsVisible(index); 317 elementPopup.show(list, e.getX(), e.getY()); 318 } 319 } 320 } 321 322 @Override 323 public void mouseMoved(MouseEvent e) { 324 super.mouseMoved(e); 325 int idx = locationToIndex(e.getPoint()); 326 if (idx == -1) { 327 return; 328 } 329 330 SelectorItem item = (SelectorItem) model.getElementAt(idx); 331 list.setToolTipText("<html><pre style='width:300px;'>" + 332 Utils.escapeReservedCharactersHTML(Utils.restrictStringLines(item.getQuery(), 9))); 333 } 334 335 private void initPopupMenus() { 336 AbstractAction add = new AbstractAction(tr("Add")) { 337 @Override 338 public void actionPerformed(ActionEvent e) { 339 createNewItem(); 340 } 341 }; 342 AbstractAction edit = new AbstractAction(tr("Edit")) { 343 @Override 344 public void actionPerformed(ActionEvent e) { 345 editSelectedItem(); 346 } 347 }; 348 AbstractAction remove = new AbstractAction(tr("Remove")) { 349 @Override 350 public void actionPerformed(ActionEvent e) { 351 removeSelectedItem(); 352 } 353 }; 354 this.emptySelectionPopup.add(add); 355 this.elementPopup.add(add); 356 this.elementPopup.add(edit); 357 this.elementPopup.add(remove); 358 } 359 } 360 361 /** 362 * This class defines the way each element is rendered in the list. 363 */ 364 private static class OverpassQueryCellRendered extends JLabel implements ListCellRenderer<SelectorItem> { 365 366 OverpassQueryCellRendered() { 367 setOpaque(true); 368 } 369 370 @Override 371 public Component getListCellRendererComponent( 372 JList<? extends SelectorItem> list, 373 SelectorItem value, 374 int index, 375 boolean isSelected, 376 boolean cellHasFocus) { 377 378 Font font = list.getFont(); 379 if (isSelected) { 380 setFont(new Font(font.getFontName(), Font.BOLD, font.getSize() + 2)); 381 setBackground(list.getSelectionBackground()); 382 setForeground(list.getSelectionForeground()); 383 } else { 384 setFont(new Font(font.getFontName(), Font.PLAIN, font.getSize() + 2)); 385 setBackground(list.getBackground()); 386 setForeground(list.getForeground()); 387 } 388 389 setEnabled(list.isEnabled()); 390 setText(value.getKey()); 391 392 if (isSelected && cellHasFocus) { 393 setBorder(new CompoundBorder( 394 BorderFactory.createLineBorder(Color.BLACK, 1), 395 BorderFactory.createEmptyBorder(2, 0, 2, 0))); 396 } else { 397 setBorder(new CompoundBorder( 398 null, 399 BorderFactory.createEmptyBorder(2, 0, 2, 0))); 400 } 401 402 return this; 403 } 404 } 405 406 /** 407 * Dialog that provides functionality to add/edit an item from the list. 408 */ 409 private final class EditItemDialog extends ExtendedDialog { 410 411 private final JTextField name; 412 private final JosmTextArea query; 413 private final int initialNameHash; 414 415 private final transient AbstractTextComponentValidator queryValidator; 416 private final transient AbstractTextComponentValidator nameValidator; 417 418 private static final int SUCCESS_BTN = 0; 419 private static final int CANCEL_BTN = 1; 420 421 /** 422 * Added/Edited object to be returned. If {@link Optional#empty()} then probably 423 * the user closed the dialog, otherwise {@link SelectorItem} is present. 424 */ 425 private transient Optional<SelectorItem> outputItem = Optional.empty(); 426 427 EditItemDialog(Component parent, String title, String... buttonTexts) { 428 this(parent, title, "", "", buttonTexts); 429 } 430 431 EditItemDialog( 432 Component parent, 433 String title, 434 String nameToEdit, 435 String queryToEdit, 436 String... buttonTexts) { 437 super(parent, title, buttonTexts); 438 439 this.initialNameHash = nameToEdit.hashCode(); 440 441 this.name = new JTextField(nameToEdit); 442 this.query = new JosmTextArea(queryToEdit); 443 444 this.queryValidator = new DefaultTextComponentValidator(this.query, "", tr("Query cannot be empty")); 445 this.nameValidator = new AbstractTextComponentValidator(this.name) { 446 @Override 447 public void validate() { 448 if (isValid()) { 449 feedbackValid(tr("This name can be used for the item")); 450 } else { 451 feedbackInvalid(tr("Item with this name already exists")); 452 } 453 } 454 455 @Override 456 public boolean isValid() { 457 String currentName = name.getText(); 458 int currentHash = currentName.hashCode(); 459 460 return !Utils.isStripEmpty(currentName) && 461 !(currentHash != initialNameHash && 462 items.containsKey(currentName)); 463 } 464 }; 465 466 this.name.getDocument().addDocumentListener(this.nameValidator); 467 this.query.getDocument().addDocumentListener(this.queryValidator); 468 469 JPanel panel = new JPanel(new GridBagLayout()); 470 JScrollPane queryScrollPane = GuiHelper.embedInVerticalScrollPane(this.query); 471 queryScrollPane.getVerticalScrollBar().setUnitIncrement(10); // make scrolling smooth 472 473 GBC constraint = GBC.eol().insets(8, 0, 8, 8).anchor(GBC.CENTER).fill(GBC.HORIZONTAL); 474 constraint.ipady = 250; 475 panel.add(this.name, GBC.eol().insets(5).anchor(GBC.SOUTHEAST).fill(GBC.HORIZONTAL)); 476 panel.add(queryScrollPane, constraint); 477 478 setDefaultButton(SUCCESS_BTN); 479 setCancelButton(CANCEL_BTN); 480 setPreferredSize(new Dimension(400, 400)); 481 setContent(panel, false); 482 } 483 484 /** 485 * Gets a new {@link SelectorItem} if one was created/modified. 486 * @return A {@link SelectorItem} object created out of the fields of the dialog. 487 */ 488 public Optional<SelectorItem> getOutputItem() { 489 return this.outputItem; 490 } 491 492 @Override 493 protected void buttonAction(int buttonIndex, ActionEvent evt) { 494 if (buttonIndex == SUCCESS_BTN) { 495 if (!this.nameValidator.isValid()) { 496 JOptionPane.showMessageDialog( 497 componentParent, 498 tr("The item cannot be created with provided name"), 499 tr("Warning"), 500 JOptionPane.WARNING_MESSAGE); 501 } else if (!this.queryValidator.isValid()) { 502 JOptionPane.showMessageDialog( 503 componentParent, 504 tr("The item cannot be created with an empty query"), 505 tr("Warning"), 506 JOptionPane.WARNING_MESSAGE); 507 } else { 508 this.outputItem = Optional.of(new SelectorItem(this.name.getText(), this.query.getText())); 509 super.buttonAction(buttonIndex, evt); 510 } 511 } else { 512 super.buttonAction(buttonIndex, evt); 513 } 514 } 515 } 516 517 /** 518 * This class represents an Overpass query used by the user that can be 519 * shown within {@link OverpassQueryList}. 520 */ 521 public static class SelectorItem { 522 private final String itemKey; 523 private final String query; 524 private int usageCount; 525 526 /** 527 * Constructs a new {@code SelectorItem}. 528 * @param key The key of this item. 529 * @param query The query of the item. 530 * @exception NullPointerException if any parameter is {@code null}. 531 * @exception IllegalArgumentException if any parameter is empty. 532 */ 533 public SelectorItem(String key, String query) { 534 this(key, query, 1); 535 } 536 537 /** 538 * Constructs a new {@code SelectorItem}. 539 * @param key The key of this item. 540 * @param query The query of the item. 541 * @param usageCount The number of times this query was used. 542 * @exception NullPointerException if any parameter is {@code null}. 543 * @exception IllegalArgumentException if any parameter is empty. 544 */ 545 public SelectorItem(String key, String query, int usageCount) { 546 Objects.requireNonNull(key); 547 Objects.requireNonNull(query); 548 549 if (Utils.isStripEmpty(key)) { 550 throw new IllegalArgumentException("The key of the item cannot be empty"); 551 } 552 if (Utils.isStripEmpty(query)) { 553 throw new IllegalArgumentException("The query cannot be empty"); 554 } 555 556 this.itemKey = key; 557 this.query = query; 558 this.usageCount = usageCount; 559 } 560 561 /** 562 * Gets the key (a string that is displayed in the selector) of this item. 563 * @return A string representing the key of this item. 564 */ 565 public String getKey() { 566 return this.itemKey; 567 } 568 569 /** 570 * Gets the overpass query of this item. 571 * @return A string representing the overpass query of this item. 572 */ 573 public String getQuery() { 574 return this.query; 575 } 576 577 /** 578 * Gets the number of times the query was used by the user. 579 * @return The usage count of this item. 580 */ 581 public int getUsageCount() { 582 return this.usageCount; 583 } 584 585 /** 586 * Increments the {@link SelectorItem#usageCount} by one till 587 * it reaches {@link Integer#MAX_VALUE}. 588 */ 589 public void increaseUsageCount() { 590 if (this.usageCount < Integer.MAX_VALUE) { 591 this.usageCount++; 592 } 593 } 594 595 /** 596 * Decrements the {@link SelectorItem#usageCount} ny one till 597 * it reaches 0. 598 */ 599 public void decreaseUsageCount() { 600 if (this.usageCount > 0) { 601 this.usageCount--; 602 } 603 } 604 605 @Override 606 public boolean equals(Object o) { 607 if (this == o) return true; 608 if (!(o instanceof SelectorItem)) return false; 609 610 SelectorItem that = (SelectorItem) o; 611 612 return itemKey.equals(that.itemKey) && 613 query.equals(that.getKey()); 614 } 615 616 @Override 617 public int hashCode() { 618 return itemKey.hashCode(); 619 } 620 } 621 }