- Timestamp:
- 2017-07-10T23:12:16+02:00 (7 years ago)
- Location:
- trunk/src/org/openstreetmap/josm/actions/search
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java
r12346 r12464 36 36 import javax.swing.JPanel; 37 37 import javax.swing.JRadioButton; 38 import javax.swing.SwingUtilities; 38 39 import javax.swing.text.BadLocationException; 40 import javax.swing.text.Document; 39 41 import javax.swing.text.JTextComponent; 40 42 … … 55 57 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser; 56 58 import org.openstreetmap.josm.gui.progress.ProgressMonitor; 59 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 60 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector; 57 61 import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator; 58 62 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; … … 232 236 @Override 233 237 public void mouseClicked(MouseEvent e) { 234 try { 235 JTextComponent tf = hcb.getEditorComponent(); 236 tf.getDocument().insertString(tf.getCaretPosition(), ' ' + insertText, null); 237 } catch (BadLocationException ex) { 238 throw new JosmRuntimeException(ex.getMessage(), ex); 238 JTextComponent tf = hcb.getEditorComponent(); 239 240 /* 241 * Make sure that the focus is transferred to the search text field 242 * from the selector component. 243 */ 244 if (!tf.hasFocus()) { 245 tf.requestFocusInWindow(); 239 246 } 247 248 /* 249 * In order to make interaction with the search dialog simpler, 250 * we make sure that if autocompletion triggers and the text field is 251 * not in focus, the correct area is selected. We first request focus 252 * and then execute the selection logic. invokeLater allows us to 253 * defer the selection until waiting for focus. 254 */ 255 SwingUtilities.invokeLater(() -> { 256 try { 257 tf.getDocument().insertString(tf.getCaretPosition(), ' ' + insertText, null); 258 } catch (BadLocationException ex) { 259 throw new JosmRuntimeException(ex.getMessage(), ex); 260 } 261 }); 240 262 } 241 263 }); … … 245 267 } 246 268 269 /** 270 * Builds and shows the search dialog. 271 * @param initialValues A set of initial values needed in order to initialize the search dialog. 272 * If is {@code null}, then default settings are used. 273 * @return Returns {@link SearchAction} object containing parameters of the search. 274 */ 247 275 public static SearchSetting showSearchDialog(SearchSetting initialValues) { 248 276 if (initialValues == null) { 249 277 initialValues = new SearchSetting(); 250 278 } 251 // -- prepare the combo box with the search expressions 252 // 279 280 // prepare the combo box with the search expressions 253 281 JLabel label = new JLabel(initialValues instanceof Filter ? tr("Filter string:") : tr("Search string:")); 254 finalHistoryComboBox hcbSearchString = new HistoryComboBox();255 finalString tooltip = tr("Enter the search expression");282 HistoryComboBox hcbSearchString = new HistoryComboBox(); 283 String tooltip = tr("Enter the search expression"); 256 284 hcbSearchString.setText(initialValues.text); 257 285 hcbSearchString.setToolTipText(tooltip); 286 258 287 // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement() 259 //260 288 List<String> searchExpressionHistory = getSearchExpressionHistory(); 261 289 Collections.reverse(searchExpressionHistory); … … 274 302 bg.add(inSelection); 275 303 276 finalJCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);304 JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive); 277 305 JCheckBox allElements = new JCheckBox(tr("all objects"), initialValues.allElements); 278 306 allElements.setToolTipText(tr("Also include incomplete and deleted objects in search.")); 279 307 JCheckBox addOnToolbar = new JCheckBox(tr("add toolbar button"), false); 280 308 281 finalJRadioButton standardSearch = new JRadioButton(tr("standard"), !initialValues.regexSearch && !initialValues.mapCSSSearch);282 finalJRadioButton regexSearch = new JRadioButton(tr("regular expression"), initialValues.regexSearch);283 finalJRadioButton mapCSSSearch = new JRadioButton(tr("MapCSS selector"), initialValues.mapCSSSearch);284 finalButtonGroup bg2 = new ButtonGroup();309 JRadioButton standardSearch = new JRadioButton(tr("standard"), !initialValues.regexSearch && !initialValues.mapCSSSearch); 310 JRadioButton regexSearch = new JRadioButton(tr("regular expression"), initialValues.regexSearch); 311 JRadioButton mapCSSSearch = new JRadioButton(tr("MapCSS selector"), initialValues.mapCSSSearch); 312 ButtonGroup bg2 = new ButtonGroup(); 285 313 bg2.add(standardSearch); 286 314 bg2.add(regexSearch); 287 315 bg2.add(mapCSSSearch); 288 289 JPanel left = new JPanel(new GridBagLayout());290 316 291 317 JPanel selectionSettings = new JPanel(new GridBagLayout()); … … 300 326 additionalSettings.add(caseSensitive, GBC.eol().anchor(GBC.WEST).fill(GBC.HORIZONTAL)); 301 327 328 JPanel left = new JPanel(new GridBagLayout()); 329 302 330 left.add(selectionSettings, GBC.eol().fill(GBC.BOTH)); 303 331 left.add(additionalSettings, GBC.eol().fill(GBC.BOTH)); … … 316 344 } 317 345 318 finalJPanel right = SearchAction.buildHintsSection(hcbSearchString);319 finalJPanel top = new JPanel(new GridBagLayout());346 JPanel right = SearchAction.buildHintsSection(hcbSearchString); 347 JPanel top = new JPanel(new GridBagLayout()); 320 348 top.add(label, GBC.std().insets(0, 0, 5, 0)); 321 349 top.add(hcbSearchString, GBC.eol().fill(GBC.HORIZONTAL)); 322 350 323 final JTextComponent editorComponent = hcbSearchString.getEditorComponent(); 324 editorComponent.getDocument().addDocumentListener(new AbstractTextComponentValidator(editorComponent) { 351 JTextComponent editorComponent = hcbSearchString.getEditorComponent(); 352 Document document = editorComponent.getDocument(); 353 354 /* 355 * Setup the logic to validate the contents of the search text field which is executed 356 * every time the content of the field has changed. If the query is incorrect, then 357 * the text field is colored red. 358 */ 359 document.addDocumentListener(new AbstractTextComponentValidator(editorComponent) { 325 360 326 361 @Override … … 349 384 }); 350 385 351 final JPanel p = new JPanel(new GridBagLayout()); 386 /* 387 * Setup the logic to append preset queries to the search text field according to 388 * selected preset by the user. Every query is of the form ' group/sub-group/.../presetName' 389 * if the corresponding group of the preset exists, otherwise it is simply ' presetName'. 390 */ 391 TaggingPresetSelector selector = new TaggingPresetSelector(false, false); 392 selector.setBorder(BorderFactory.createTitledBorder(tr("Search by preset"))); 393 selector.setDblClickListener(ev -> setPresetDblClickListener(selector, editorComponent)); 394 395 JPanel p = new JPanel(new GridBagLayout()); 352 396 p.add(top, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 0)); 353 397 p.add(left, GBC.std().anchor(GBC.NORTH).insets(5, 10, 10, 0).fill(GBC.VERTICAL)); 354 p.add(right, GBC.eol().fill(GBC.BOTH).insets(0, 10, 0, 0)); 398 p.add(right, GBC.std().fill(GBC.BOTH).insets(0, 10, 0, 0)); 399 p.add(selector, GBC.eol().fill(GBC.BOTH).insets(0, 10, 0, 0)); 355 400 356 401 ExtendedDialog dialog = new ExtendedDialog( … … 391 436 392 437 // User pressed OK - let's perform the search 393 SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace394 : (add.isSelected() ? SearchAction.SearchMode.add395 : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection));396 438 initialValues.text = hcbSearchString.getText(); 397 initialValues.mode = mode;398 439 initialValues.caseSensitive = caseSensitive.isSelected(); 399 440 initialValues.allElements = allElements.isSelected(); 400 441 initialValues.regexSearch = regexSearch.isSelected(); 401 442 initialValues.mapCSSSearch = mapCSSSearch.isSelected(); 443 444 if (inSelection.isSelected()) { 445 initialValues.mode = SearchAction.SearchMode.in_selection; 446 } else if (replace.isSelected()) { 447 initialValues.mode = SearchAction.SearchMode.replace; 448 } else if (add.isSelected()) { 449 initialValues.mode = SearchAction.SearchMode.add; 450 } else { 451 initialValues.mode = SearchAction.SearchMode.remove; 452 } 402 453 403 454 if (addOnToolbar.isSelected()) { … … 414 465 Main.toolbar.addCustomButton(res, -1, false); 415 466 } 467 416 468 return initialValues; 417 469 } … … 458 510 .addKeyword("untagged", "untagged ", tr("object without useful tags")), 459 511 GBC.eol()); 512 hintPanel.add(new SearchKeywordRow(hcbSearchString) 513 .addKeyword("preset:\"Annotation/Address\"", "preset:\"Annotation/Address\"", 514 tr("all objects that use the address preset")) 515 .addKeyword("preset:\"Geography/Nature/*\"", "preset:\"Geography/Nature/*\"", 516 tr("all objects that use any preset under the Geography/Nature group")), 517 GBC.eol().anchor(GBC.CENTER)); 460 518 hintPanel.add(new SearchKeywordRow(hcbSearchString) 461 519 .addTitle(tr("metadata")) … … 576 634 577 635 /** 636 * 637 * @param selector Selector component that the user interacts with 638 * @param searchEditor Editor for search queries 639 */ 640 private static void setPresetDblClickListener(TaggingPresetSelector selector, JTextComponent searchEditor) { 641 TaggingPreset selectedPreset = selector.getSelectedPresetAndUpdateClassification(); 642 643 if (selectedPreset == null) { 644 return; 645 } 646 647 /* 648 * Make sure that the focus is transferred to the search text field 649 * from the selector component. 650 */ 651 searchEditor.requestFocusInWindow(); 652 653 /* 654 * In order to make interaction with the search dialog simpler, 655 * we make sure that if autocompletion triggers and the text field is 656 * not in focus, the correct area is selected. We first request focus 657 * and then execute the selection logic. invokeLater allows us to 658 * defer the selection until waiting for focus. 659 */ 660 SwingUtilities.invokeLater(() -> { 661 int textOffset = searchEditor.getCaretPosition(); 662 String presetSearchQuery = " preset:" + 663 "\"" + selectedPreset.getRawName() + "\""; 664 try { 665 searchEditor.getDocument().insertString(textOffset, presetSearchQuery, null); 666 } catch (BadLocationException e1) { 667 throw new JosmRuntimeException(e1.getMessage(), e1); 668 } 669 }); 670 } 671 672 /** 578 673 * Interfaces implementing this may receive the result of the current search. 579 674 * @author Michael Zangl … … 750 845 } 751 846 847 /** 848 * This class defines a set of parameters that is used to 849 * perform search within the search dialog. 850 */ 752 851 public static class SearchSetting { 753 852 public String text; … … 811 910 } 812 911 912 /** 913 * <p>Transforms a string following a certain format, namely "[R | A | D | S][C?,R?,A?,M?] [a-zA-Z]" 914 * where the first part defines the mode of the search, see {@link SearchMode}, the second defines 915 * a set of attributes within the {@code SearchSetting} class and the second is the search query. 916 * <p> 917 * Attributes are as follows: 918 * <ul> 919 * <li>C - if search is case sensitive 920 * <li>R - if the regex syntax is used 921 * <li>A - if all objects are considered 922 * <li>M - if the mapCSS syntax is used 923 * </ul> 924 * <p>For example, "RC type:node" is a valid string representation of an object that replaces the 925 * current selection, is case sensitive and searches for all objects of type node. 926 * @param s A string representation of a {@code SearchSetting} object 927 * from which the object must be built. 928 * @return A {@code SearchSetting} defined by the input string. 929 */ 813 930 public static SearchSetting readFromString(String s) { 814 931 if (s.isEmpty()) … … 852 969 } 853 970 971 /** 972 * Builds a string representation of the {@code SearchSetting} object, 973 * see {@link #readFromString(String)} for more details. 974 * @return A string representation of the {@code SearchSetting} object. 975 */ 854 976 public String writeToString() { 855 977 if (text == null || text.isEmpty()) -
trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java
r11978 r12464 21 21 import java.util.regex.Pattern; 22 22 import java.util.regex.PatternSyntaxException; 23 import java.util.stream.Collectors; 23 24 24 25 import org.openstreetmap.josm.Main; … … 39 40 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser; 40 41 import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException; 42 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 43 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu; 44 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSeparator; 45 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 41 46 import org.openstreetmap.josm.tools.AlphanumComparator; 42 47 import org.openstreetmap.josm.tools.Geometry; … … 116 121 "changeset", "nodes", "ways", "tags", "areasize", "waylength", "modified", "deleted", "selected", 117 122 "incomplete", "untagged", "closed", "new", "indownloadedarea", 118 "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole" );123 "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole", "preset"); 119 124 120 125 @Override … … 152 157 case "type": 153 158 return new ExactType(tokenizer.readTextOrNumber()); 159 case "preset": 160 return new Preset(tokenizer.readTextOrNumber()); 154 161 case "user": 155 162 return new UserMatch(tokenizer.readTextOrNumber()); … … 1555 1562 } 1556 1563 1564 /** 1565 * Matches presets. 1566 * @since 12464 1567 */ 1568 private static class Preset extends Match { 1569 private final List<TaggingPreset> presets; 1570 1571 Preset(String presetName) throws ParseError { 1572 1573 if (presetName == null || presetName.equals("")) { 1574 throw new ParseError("The name of the preset is required"); 1575 } 1576 1577 int wildCardIdx = presetName.lastIndexOf("*"); 1578 int length = presetName.length() - 1; 1579 1580 /* 1581 * Match strictly (simply comparing the names) if there is no '*' symbol 1582 * at the end of the name or '*' is a part of the preset name. 1583 */ 1584 boolean matchStrictly = wildCardIdx == -1 || wildCardIdx != length; 1585 1586 this.presets = TaggingPresets.getTaggingPresets() 1587 .stream() 1588 .filter(preset -> !(preset instanceof TaggingPresetMenu || preset instanceof TaggingPresetSeparator)) 1589 .filter(preset -> this.presetNameMatch(presetName, preset, matchStrictly)) 1590 .collect(Collectors.toList()); 1591 1592 if (this.presets.isEmpty()) { 1593 throw new ParseError(tr("Unknown preset name: ") + presetName); 1594 } 1595 } 1596 1597 @Override 1598 public boolean match(OsmPrimitive osm) { 1599 for (TaggingPreset preset : this.presets) { 1600 if (preset.test(osm)) { 1601 return true; 1602 } 1603 } 1604 1605 return false; 1606 } 1607 1608 private boolean presetNameMatch(String name, TaggingPreset preset, boolean matchStrictly) { 1609 if (matchStrictly) { 1610 return name.equalsIgnoreCase(preset.getRawName()); 1611 } 1612 1613 try { 1614 String groupSuffix = name.substring(0, name.length() - 2); // try to remove '/*' 1615 TaggingPresetMenu group = preset.group; 1616 1617 return group != null && groupSuffix.equalsIgnoreCase(group.getRawName()); 1618 } catch (StringIndexOutOfBoundsException ex) { 1619 return false; 1620 } 1621 } 1622 } 1623 1557 1624 public static class ParseError extends Exception { 1558 1625 public ParseError(String msg) { … … 1805 1872 } 1806 1873 } 1874
Note:
See TracChangeset
for help on using the changeset viewer.