Changeset 12464 in josm
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 -
trunk/test/unit/org/openstreetmap/josm/actions/search/SearchCompilerTest.java
r11978 r12464 8 8 import static org.junit.Assert.fail; 9 9 10 import java.lang.reflect.Field; 10 11 import java.nio.charset.StandardCharsets; 11 12 import java.nio.file.Files; 12 13 import java.nio.file.Paths; 14 import java.util.Arrays; 15 import java.util.Collection; 16 import java.util.Collections; 13 17 14 18 import org.junit.Rule; … … 31 35 import org.openstreetmap.josm.data.osm.Way; 32 36 import org.openstreetmap.josm.data.osm.WayData; 37 import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset; 38 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetType; 39 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets; 40 import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu; 41 import org.openstreetmap.josm.gui.tagging.presets.items.Key; 33 42 import org.openstreetmap.josm.testutils.JOSMTestRules; 34 43 import org.openstreetmap.josm.tools.date.DateUtils; … … 491 500 TestUtils.superficialEnumCodeCoverage(ExactKeyValue.Mode.class); 492 501 } 502 503 /** 504 * Robustness test for preset searching. Ensures that the query 'preset:' is not accepted. 505 * @throws ParseError always 506 * @since 12464 507 */ 508 @Test(expected = ParseError.class) 509 public void testPresetSearchMissingValue() throws ParseError { 510 SearchSetting settings = new SearchSetting(); 511 settings.text = "preset:"; 512 settings.mapCSSSearch = false; 513 514 TaggingPresets.readFromPreferences(); 515 516 SearchCompiler.compile(settings); 517 } 518 519 /** 520 * Robustness test for preset searching. Validates that it is not possible to search for 521 * non existing presets. 522 * @throws ParseError always 523 * @since 12464 524 */ 525 @Test(expected = ParseError.class) 526 public void testPresetNotExist() throws ParseError { 527 String testPresetName = "groupnamethatshouldnotexist/namethatshouldnotexist"; 528 SearchSetting settings = new SearchSetting(); 529 settings.text = "preset:" + testPresetName; 530 settings.mapCSSSearch = false; 531 532 // load presets 533 TaggingPresets.readFromPreferences(); 534 535 SearchCompiler.compile(settings); 536 } 537 538 /** 539 * Robustness tests for preset searching. Ensures that combined preset names (having more than 540 * 1 word) must be enclosed with " . 541 * @throws ParseError always 542 * @since 12464 543 */ 544 @Test(expected = ParseError.class) 545 public void testPresetMultipleWords() throws ParseError { 546 TaggingPreset testPreset = new TaggingPreset(); 547 testPreset.name = "Test Combined Preset Name"; 548 testPreset.group = new TaggingPresetMenu(); 549 testPreset.group.name = "TestGroupName"; 550 551 String combinedPresetname = testPreset.getRawName(); 552 SearchSetting settings = new SearchSetting(); 553 settings.text = "preset:" + combinedPresetname; 554 settings.mapCSSSearch = false; 555 556 // load presets 557 TaggingPresets.readFromPreferences(); 558 559 SearchCompiler.compile(settings); 560 } 561 562 563 /** 564 * Ensures that correct presets are stored in the {@link org.openstreetmap.josm.actions.search.SearchCompiler.Preset} 565 * class against which the osm primitives are tested. 566 * @throws ParseError if an error has been encountered while compiling 567 * @throws NoSuchFieldException if there is no field called 'presets' 568 * @throws IllegalAccessException if cannot access the field where all matching presets are stored 569 * @since 12464 570 */ 571 @Test 572 public void testPresetLookup() throws ParseError, NoSuchFieldException, IllegalAccessException { 573 TaggingPreset testPreset = new TaggingPreset(); 574 testPreset.name = "Test Preset Name"; 575 testPreset.group = new TaggingPresetMenu(); 576 testPreset.group.name = "Test Preset Group Name"; 577 578 String query = "preset:" + 579 "\"" + testPreset.getRawName() + "\""; 580 SearchSetting settings = new SearchSetting(); 581 settings.text = query; 582 settings.mapCSSSearch = false; 583 584 // load presets and add the test preset 585 TaggingPresets.readFromPreferences(); 586 TaggingPresets.addTaggingPresets(Collections.singletonList(testPreset)); 587 588 Match match = SearchCompiler.compile(settings); 589 590 // access the private field where all matching presets are stored 591 // and ensure that indeed the correct ones are there 592 Field field = match.getClass().getDeclaredField("presets"); 593 field.setAccessible(true); 594 Collection<TaggingPreset> foundPresets = (Collection<TaggingPreset>) field.get(match); 595 596 assertEquals(1, foundPresets.size()); 597 assertTrue(foundPresets.contains(testPreset)); 598 } 599 600 /** 601 * Ensures that the wildcard search works and that correct presets are stored in 602 * the {@link org.openstreetmap.josm.actions.search.SearchCompiler.Preset} class against which 603 * the osm primitives are tested. 604 * @throws ParseError if an error has been encountered while compiling 605 * @throws NoSuchFieldException if there is no field called 'presets' 606 * @throws IllegalAccessException if cannot access the field where all matching presets are stored 607 * @since 12464 608 */ 609 @Test 610 public void testPresetLookupWildcard() throws ParseError, NoSuchFieldException, IllegalAccessException { 611 TaggingPresetMenu group = new TaggingPresetMenu(); 612 group.name = "TestPresetGroup"; 613 614 TaggingPreset testPreset1 = new TaggingPreset(); 615 testPreset1.name = "TestPreset1"; 616 testPreset1.group = group; 617 618 TaggingPreset testPreset2 = new TaggingPreset(); 619 testPreset2.name = "TestPreset2"; 620 testPreset2.group = group; 621 622 TaggingPreset testPreset3 = new TaggingPreset(); 623 testPreset3.name = "TestPreset3"; 624 testPreset3.group = group; 625 626 String query = "preset:" + "\"" + group.getRawName() + "/*\""; 627 SearchSetting settings = new SearchSetting(); 628 settings.text = query; 629 settings.mapCSSSearch = false; 630 631 TaggingPresets.readFromPreferences(); 632 TaggingPresets.addTaggingPresets(Arrays.asList(testPreset1, testPreset2, testPreset3)); 633 634 Match match = SearchCompiler.compile(settings); 635 636 // access the private field where all matching presets are stored 637 // and ensure that indeed the correct ones are there 638 Field field = match.getClass().getDeclaredField("presets"); 639 field.setAccessible(true); 640 Collection<TaggingPreset> foundPresets = (Collection<TaggingPreset>) field.get(match); 641 642 assertEquals(3, foundPresets.size()); 643 assertTrue(foundPresets.contains(testPreset1)); 644 assertTrue(foundPresets.contains(testPreset2)); 645 assertTrue(foundPresets.contains(testPreset3)); 646 } 647 648 /** 649 * Ensures that correct primitives are matched against the specified preset. 650 * @throws ParseError if an error has been encountered while compiling 651 * @since 12464 652 */ 653 @Test 654 public void testPreset() throws ParseError { 655 final String presetName = "Test Preset Name"; 656 final String presetGroupName = "Test Preset Group"; 657 final String key = "test_key1"; 658 final String val = "test_val1"; 659 660 Key key1 = new Key(); 661 key1.key = key; 662 key1.value = val; 663 664 TaggingPreset testPreset = new TaggingPreset(); 665 testPreset.name = presetName; 666 testPreset.types = Collections.singleton(TaggingPresetType.NODE); 667 testPreset.data.add(key1); 668 testPreset.group = new TaggingPresetMenu(); 669 testPreset.group.name = presetGroupName; 670 671 TaggingPresets.readFromPreferences(); 672 TaggingPresets.addTaggingPresets(Collections.singleton(testPreset)); 673 674 String query = "preset:" + "\"" + testPreset.getRawName() + "\""; 675 676 SearchContext ctx = new SearchContext(query); 677 ctx.n1.put(key, val); 678 ctx.n2.put(key, val); 679 680 for (OsmPrimitive osm : new OsmPrimitive[] {ctx.n1, ctx.n2}) { 681 ctx.match(osm, true); 682 } 683 684 for (OsmPrimitive osm : new OsmPrimitive[] {ctx.r1, ctx.r2, ctx.w1, ctx.w2}) { 685 ctx.match(osm, false); 686 } 687 } 493 688 } 689
Note:
See TracChangeset
for help on using the changeset viewer.