Ignore:
Timestamp:
2017-07-10T23:12:16+02:00 (2 years ago)
Author:
michael2402
Message:

Apply #14923: Adjust the search dialog to allow to search for primitives that use a preset. Patch by bafonins

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  
    3636import javax.swing.JPanel;
    3737import javax.swing.JRadioButton;
     38import javax.swing.SwingUtilities;
    3839import javax.swing.text.BadLocationException;
     40import javax.swing.text.Document;
    3941import javax.swing.text.JTextComponent;
    4042
     
    5557import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser;
    5658import org.openstreetmap.josm.gui.progress.ProgressMonitor;
     59import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     60import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSelector;
    5761import org.openstreetmap.josm.gui.widgets.AbstractTextComponentValidator;
    5862import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
     
    232236                    @Override
    233237                    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();
    239246                        }
     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                        });
    240262                    }
    241263                });
     
    245267    }
    246268
     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     */
    247275    public static SearchSetting showSearchDialog(SearchSetting initialValues) {
    248276        if (initialValues == null) {
    249277            initialValues = new SearchSetting();
    250278        }
    251         // -- prepare the combo box with the search expressions
    252         //
     279
     280        // prepare the combo box with the search expressions
    253281        JLabel label = new JLabel(initialValues instanceof Filter ? tr("Filter string:") : tr("Search string:"));
    254         final HistoryComboBox hcbSearchString = new HistoryComboBox();
    255         final String tooltip = tr("Enter the search expression");
     282        HistoryComboBox hcbSearchString = new HistoryComboBox();
     283        String tooltip = tr("Enter the search expression");
    256284        hcbSearchString.setText(initialValues.text);
    257285        hcbSearchString.setToolTipText(tooltip);
     286
    258287        // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement()
    259         //
    260288        List<String> searchExpressionHistory = getSearchExpressionHistory();
    261289        Collections.reverse(searchExpressionHistory);
     
    274302        bg.add(inSelection);
    275303
    276         final JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
     304        JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
    277305        JCheckBox allElements = new JCheckBox(tr("all objects"), initialValues.allElements);
    278306        allElements.setToolTipText(tr("Also include incomplete and deleted objects in search."));
    279307        JCheckBox addOnToolbar = new JCheckBox(tr("add toolbar button"), false);
    280308
    281         final JRadioButton standardSearch = new JRadioButton(tr("standard"), !initialValues.regexSearch && !initialValues.mapCSSSearch);
    282         final JRadioButton regexSearch = new JRadioButton(tr("regular expression"), initialValues.regexSearch);
    283         final JRadioButton mapCSSSearch = new JRadioButton(tr("MapCSS selector"), initialValues.mapCSSSearch);
    284         final ButtonGroup 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();
    285313        bg2.add(standardSearch);
    286314        bg2.add(regexSearch);
    287315        bg2.add(mapCSSSearch);
    288 
    289         JPanel left = new JPanel(new GridBagLayout());
    290316
    291317        JPanel selectionSettings = new JPanel(new GridBagLayout());
     
    300326        additionalSettings.add(caseSensitive, GBC.eol().anchor(GBC.WEST).fill(GBC.HORIZONTAL));
    301327
     328        JPanel left = new JPanel(new GridBagLayout());
     329
    302330        left.add(selectionSettings, GBC.eol().fill(GBC.BOTH));
    303331        left.add(additionalSettings, GBC.eol().fill(GBC.BOTH));
     
    316344        }
    317345
    318         final JPanel right = SearchAction.buildHintsSection(hcbSearchString);
    319         final JPanel top = new JPanel(new GridBagLayout());
     346        JPanel right = SearchAction.buildHintsSection(hcbSearchString);
     347        JPanel top = new JPanel(new GridBagLayout());
    320348        top.add(label, GBC.std().insets(0, 0, 5, 0));
    321349        top.add(hcbSearchString, GBC.eol().fill(GBC.HORIZONTAL));
    322350
    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) {
    325360
    326361            @Override
     
    349384        });
    350385
    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());
    352396        p.add(top, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 0));
    353397        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));
    355400
    356401        ExtendedDialog dialog = new ExtendedDialog(
     
    391436
    392437        // User pressed OK - let's perform the search
    393         SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace
    394                 : (add.isSelected() ? SearchAction.SearchMode.add
    395                         : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection));
    396438        initialValues.text = hcbSearchString.getText();
    397         initialValues.mode = mode;
    398439        initialValues.caseSensitive = caseSensitive.isSelected();
    399440        initialValues.allElements = allElements.isSelected();
    400441        initialValues.regexSearch = regexSearch.isSelected();
    401442        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        }
    402453
    403454        if (addOnToolbar.isSelected()) {
     
    414465            Main.toolbar.addCustomButton(res, -1, false);
    415466        }
     467
    416468        return initialValues;
    417469    }
     
    458510                .addKeyword("untagged", "untagged ", tr("object without useful tags")),
    459511                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));
    460518            hintPanel.add(new SearchKeywordRow(hcbSearchString)
    461519                .addTitle(tr("metadata"))
     
    576634
    577635    /**
     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    /**
    578673     * Interfaces implementing this may receive the result of the current search.
    579674     * @author Michael Zangl
     
    750845    }
    751846
     847    /**
     848     * This class defines a set of parameters that is used to
     849     * perform search within the search dialog.
     850     */
    752851    public static class SearchSetting {
    753852        public String text;
     
    811910        }
    812911
     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         */
    813930        public static SearchSetting readFromString(String s) {
    814931            if (s.isEmpty())
     
    852969        }
    853970
     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         */
    854976        public String writeToString() {
    855977            if (text == null || text.isEmpty())
  • trunk/src/org/openstreetmap/josm/actions/search/SearchCompiler.java

    r11978 r12464  
    2121import java.util.regex.Pattern;
    2222import java.util.regex.PatternSyntaxException;
     23import java.util.stream.Collectors;
    2324
    2425import org.openstreetmap.josm.Main;
     
    3940import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.MapCSSParser;
    4041import org.openstreetmap.josm.gui.mappaint.mapcss.parsergen.ParseException;
     42import org.openstreetmap.josm.gui.tagging.presets.TaggingPreset;
     43import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetMenu;
     44import org.openstreetmap.josm.gui.tagging.presets.TaggingPresetSeparator;
     45import org.openstreetmap.josm.gui.tagging.presets.TaggingPresets;
    4146import org.openstreetmap.josm.tools.AlphanumComparator;
    4247import org.openstreetmap.josm.tools.Geometry;
     
    116121                "changeset", "nodes", "ways", "tags", "areasize", "waylength", "modified", "deleted", "selected",
    117122                "incomplete", "untagged", "closed", "new", "indownloadedarea",
    118                 "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole");
     123                "allindownloadedarea", "inview", "allinview", "timestamp", "nth", "nth%", "hasRole", "preset");
    119124
    120125        @Override
     
    152157                    case "type":
    153158                        return new ExactType(tokenizer.readTextOrNumber());
     159                    case "preset":
     160                        return new Preset(tokenizer.readTextOrNumber());
    154161                    case "user":
    155162                        return new UserMatch(tokenizer.readTextOrNumber());
     
    15551562    }
    15561563
     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
    15571624    public static class ParseError extends Exception {
    15581625        public ParseError(String msg) {
     
    18051872    }
    18061873}
     1874
Note: See TracChangeset for help on using the changeset viewer.