source: josm/trunk/src/org/openstreetmap/josm/actions/search/SearchAction.java @ 5241

Revision 5198, 34.6 KB checked in by simon04, 5 weeks ago (diff)

see #7230 - make alternative search dialog default (for testing)

  • Property svn:eol-style set to native
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.actions.search;
3
4import static org.openstreetmap.josm.gui.help.HelpUtil.ht;
5import static org.openstreetmap.josm.tools.I18n.tr;
6import static org.openstreetmap.josm.tools.I18n.trc;
7
8import java.awt.Cursor;
9import java.awt.Dimension;
10import java.awt.FlowLayout;
11import java.awt.Font;
12import java.awt.GridBagLayout;
13import java.awt.event.ActionEvent;
14import java.awt.event.KeyEvent;
15import java.awt.event.MouseAdapter;
16import java.awt.event.MouseEvent;
17import java.util.ArrayList;
18import java.util.Arrays;
19import java.util.Collection;
20import java.util.Collections;
21import java.util.HashSet;
22import java.util.LinkedList;
23import java.util.List;
24import java.util.Map;
25
26import javax.swing.ButtonGroup;
27import javax.swing.JCheckBox;
28import javax.swing.JLabel;
29import javax.swing.JOptionPane;
30import javax.swing.JPanel;
31import javax.swing.JRadioButton;
32import javax.swing.JTextField;
33import javax.swing.text.BadLocationException;
34
35import org.openstreetmap.josm.Main;
36import org.openstreetmap.josm.actions.ActionParameter;
37import org.openstreetmap.josm.actions.JosmAction;
38import org.openstreetmap.josm.actions.ParameterizedAction;
39import org.openstreetmap.josm.actions.ActionParameter.SearchSettingsActionParameter;
40import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
41import org.openstreetmap.josm.data.osm.DataSet;
42import org.openstreetmap.josm.data.osm.Filter;
43import org.openstreetmap.josm.data.osm.OsmPrimitive;
44import org.openstreetmap.josm.gui.ExtendedDialog;
45import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
46import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser;
47import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
48import org.openstreetmap.josm.tools.GBC;
49import org.openstreetmap.josm.tools.Property;
50import org.openstreetmap.josm.tools.Shortcut;
51import org.openstreetmap.josm.tools.Utils;
52
53public class SearchAction extends JosmAction implements ParameterizedAction {
54
55    public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15;
56
57    private static final String SEARCH_EXPRESSION = "searchExpression";
58
59    public static enum SearchMode {
60        replace('R'), add('A'), remove('D'), in_selection('S');
61
62        private final char code;
63
64        SearchMode(char code) {
65            this.code = code;
66        }
67
68        public char getCode() {
69            return code;
70        }
71
72        public static SearchMode fromCode(char code) {
73            for (SearchMode mode: values()) {
74                if (mode.getCode() == code)
75                    return mode;
76            }
77            return null;
78        }
79    }
80
81    private static LinkedList<SearchSetting> searchHistory = null;
82
83    public static Collection<SearchSetting> getSearchHistory() {
84        if (searchHistory == null) {
85            searchHistory = new LinkedList<SearchSetting>();
86            for (String s: Main.pref.getCollection("search.history", Collections.<String>emptyList())) {
87                SearchSetting ss = SearchSetting.readFromString(s);
88                if (ss != null) {
89                    searchHistory.add(ss);
90                }
91            }
92        }
93
94        return searchHistory;
95    }
96
97    public static void saveToHistory(SearchSetting s) {
98        if(searchHistory.isEmpty() || !s.equals(searchHistory.getFirst())) {
99            searchHistory.addFirst(new SearchSetting(s));
100        }
101        int maxsize = Main.pref.getInteger("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE);
102        while (searchHistory.size() > maxsize) {
103            searchHistory.removeLast();
104        }
105        List<String> savedHistory = new ArrayList<String>();
106        for (SearchSetting item: searchHistory) {
107            savedHistory.add(item.writeToString());
108        }
109        Main.pref.putCollection("search.history", savedHistory);
110    }
111
112    public static List<String> getSearchExpressionHistory() {
113        ArrayList<String> ret = new ArrayList<String>(getSearchHistory().size());
114        for (SearchSetting ss: getSearchHistory()) {
115            ret.add(ss.text);
116        }
117        return ret;
118    }
119
120    private static SearchSetting lastSearch = null;
121
122    public SearchAction() {
123        super(tr("Search..."), "dialogs/search", tr("Search for objects."),
124                Shortcut.registerShortcut("system:find", tr("Search..."), KeyEvent.VK_F, Shortcut.CTRL), true);
125        putValue("help", ht("/Action/Search"));
126    }
127
128    public void actionPerformed(ActionEvent e) {
129        if (!isEnabled())
130            return;
131        search();
132    }
133
134    public void actionPerformed(ActionEvent e, Map<String, Object> parameters) {
135        if (parameters.get(SEARCH_EXPRESSION) == null) {
136            actionPerformed(e);
137        } else {
138            searchWithoutHistory((SearchSetting) parameters.get(SEARCH_EXPRESSION));
139        }
140    }
141
142    private static class DescriptionTextBuilder {
143
144        StringBuilder s = new StringBuilder(4096);
145
146        public StringBuilder append(String string) {
147            return s.append(string);
148        }
149
150        StringBuilder appendItem(String item) {
151            return append("<li>").append(item).append("</li>\n");
152        }
153
154        StringBuilder appendItemHeader(String itemHeader) {
155            return append("<li class=\"header\">").append(itemHeader).append("</li>\n");
156        }
157
158        @Override
159        public String toString() {
160            return s.toString();
161        }
162    }
163
164    private static class SearchKeywordRow extends JPanel {
165
166        private final HistoryComboBox hcb;
167
168        public SearchKeywordRow(HistoryComboBox hcb) {
169            super(new FlowLayout(FlowLayout.LEFT));
170            this.hcb = hcb;
171        }
172
173        public SearchKeywordRow addTitle(String title) {
174            add(new JLabel(tr("{0}: ", title)));
175            return this;
176        }
177
178        public SearchKeywordRow addKeyword(String displayText, final String insertText, String description, String... examples) {
179            JLabel label = new JLabel("<html>"
180                    + "<style>td{border:1px solid gray; font-weight:normal;}</style>"
181                    + "<table><tr><td>" + displayText + "</td></tr></table></html>");
182            add(label);
183            if (description != null || examples.length > 0) {
184                label.setToolTipText("<html>"
185                        + description
186                        + (examples.length > 0 ? ("<ul><li>" + Utils.join("</li><li>", Arrays.asList(examples)) + "</li></ul>") : "")
187                        + "</html>");
188            }
189            if (insertText != null) {
190                label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
191                label.addMouseListener(new MouseAdapter() {
192
193                    @Override
194                    public void mouseClicked(MouseEvent e) {
195                        try {
196                            JTextField tf = (JTextField) hcb.getEditor().getEditorComponent();
197                            tf.getDocument().insertString(tf.getCaretPosition(), " " + insertText, null);
198                        } catch (BadLocationException ex) {
199                            throw new RuntimeException(ex.getMessage(), ex);
200                        }
201                    }
202                });
203            }
204            return this;
205        }
206    }
207
208    public static SearchSetting showSearchDialog(SearchSetting initialValues) {
209        if (initialValues == null) {
210            initialValues = new SearchSetting();
211        }
212        // -- prepare the combo box with the search expressions
213        //
214        JLabel label = new JLabel( initialValues instanceof Filter ? tr("Filter string:") : tr("Search string:"));
215        final HistoryComboBox hcbSearchString = new HistoryComboBox();
216        hcbSearchString.setText(initialValues.text);
217        hcbSearchString.setToolTipText(tr("Enter the search expression"));
218        // we have to reverse the history, because ComboBoxHistory will reverse it again
219        // in addElement()
220        //
221        List<String> searchExpressionHistory = getSearchExpressionHistory();
222        Collections.reverse(searchExpressionHistory);
223        hcbSearchString.setPossibleItems(searchExpressionHistory);
224        hcbSearchString.setPreferredSize(new Dimension(40, hcbSearchString.getPreferredSize().height));
225
226        JRadioButton replace = new JRadioButton(tr("replace selection"), initialValues.mode == SearchMode.replace);
227        JRadioButton add = new JRadioButton(tr("add to selection"), initialValues.mode == SearchMode.add);
228        JRadioButton remove = new JRadioButton(tr("remove from selection"), initialValues.mode == SearchMode.remove);
229        JRadioButton in_selection = new JRadioButton(tr("find in selection"), initialValues.mode == SearchMode.in_selection);
230        ButtonGroup bg = new ButtonGroup();
231        bg.add(replace);
232        bg.add(add);
233        bg.add(remove);
234        bg.add(in_selection);
235
236        final JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
237        JCheckBox allElements = new JCheckBox(tr("all objects"), initialValues.allElements);
238        allElements.setToolTipText(tr("Also include incomplete and deleted objects in search."));
239        final JCheckBox regexSearch   = new JCheckBox(tr("regular expression"), initialValues.regexSearch);
240        final JCheckBox addOnToolbar  = new JCheckBox(tr("add toolbar button"), false);
241
242        JPanel top = new JPanel(new GridBagLayout());
243        top.add(label, GBC.std().insets(0, 0, 5, 0));
244        top.add(hcbSearchString, GBC.eol().fill(GBC.HORIZONTAL));
245        JPanel left = new JPanel(new GridBagLayout());
246        left.add(replace, GBC.eol());
247        left.add(add, GBC.eol());
248        left.add(remove, GBC.eol());
249        left.add(in_selection, GBC.eop());
250        left.add(caseSensitive, GBC.eol());
251        if(Main.pref.getBoolean("expert", false))
252        {
253            left.add(allElements, GBC.eol());
254            left.add(regexSearch, GBC.eol());
255            left.add(addOnToolbar, GBC.eol());
256        }
257
258        final JPanel right;
259        if (Main.pref.getBoolean("dialog.search.new", true)) {
260            right = new JPanel(new GridBagLayout());
261            buildHintsNew(right, hcbSearchString);
262        } else {
263            right = new JPanel();
264            buildHints(right);
265        }
266
267        final JPanel p = new JPanel(new GridBagLayout());
268        p.add(top, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 0));
269        p.add(left, GBC.std().anchor(GBC.NORTH).insets(5, 10, 10, 0));
270        p.add(right, GBC.eol());
271        ExtendedDialog dialog = new ExtendedDialog(
272                Main.parent,
273                initialValues instanceof Filter ? tr("Filter") : tr("Search"),
274                        new String[] {
275                    initialValues instanceof Filter ? tr("Submit filter") : tr("Start Search"),
276                            tr("Cancel")}
277        ) {
278            @Override
279            protected void buttonAction(int buttonIndex, ActionEvent evt) {
280                if (buttonIndex == 0) {
281                    try {
282                        SearchCompiler.compile(hcbSearchString.getText(), caseSensitive.isSelected(), regexSearch.isSelected());
283                        super.buttonAction(buttonIndex, evt);
284                    } catch (ParseError e) {
285                        JOptionPane.showMessageDialog(
286                                Main.parent,
287                                tr("Search expression is not valid: \n\n {0}", e.getMessage()),
288                                tr("Invalid search expression"),
289                                JOptionPane.ERROR_MESSAGE);
290                    }
291                } else {
292                    super.buttonAction(buttonIndex, evt);
293                }
294            }
295        };
296        dialog.setButtonIcons(new String[] {"dialogs/search.png", "cancel.png"});
297        dialog.configureContextsensitiveHelp("/Action/Search", true /* show help button */);
298        dialog.setContent(p);
299        dialog.showDialog();
300        int result = dialog.getValue();
301
302        if(result != 1) return null;
303
304        // User pressed OK - let's perform the search
305        SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace
306                : (add.isSelected() ? SearchAction.SearchMode.add
307                        : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection));
308        initialValues.text = hcbSearchString.getText();
309        initialValues.mode = mode;
310        initialValues.caseSensitive = caseSensitive.isSelected();
311        initialValues.allElements = allElements.isSelected();
312        initialValues.regexSearch = regexSearch.isSelected();
313
314        if (addOnToolbar.isSelected()) {
315            ToolbarPreferences.ActionDefinition aDef =
316                    new ToolbarPreferences.ActionDefinition(Main.main.menu.search);
317            aDef.getParameters().put("searchExpression", initialValues);
318            // parametrized action definition is now composed
319            ActionParser actionParser = new ToolbarPreferences.ActionParser(null);
320            String res = actionParser.saveAction(aDef);
321
322            Collection<String> t = new LinkedList<String>(ToolbarPreferences.getToolString());
323            // add custom search button to toolbar preferences
324            if (!t.contains(res)) t.add(res);
325            Main.pref.putCollection("toolbar", t);
326            Main.toolbar.refreshToolbarControl();
327        }
328        return initialValues;
329    }
330
331    private static void buildHints(JPanel right) {
332        DescriptionTextBuilder descriptionText = new DescriptionTextBuilder();
333        descriptionText.append("<html><style>li.header{font-size:110%; list-style-type:none; margin-top:5px;}</style><ul>");
334        descriptionText.appendItem(tr("<b>Baker Street</b> - ''Baker'' and ''Street'' in any key"));
335        descriptionText.appendItem(tr("<b>\"Baker Street\"</b> - ''Baker Street'' in any key"));
336        descriptionText.appendItem(tr("<b>key:Bak</b> - ''Bak'' anywhere in the key ''key''"));
337        descriptionText.appendItem(tr("<b>-key:Bak</b> - ''Bak'' nowhere in the key ''key''"));
338        descriptionText.appendItem(tr("<b>key=value</b> - key ''key'' with value exactly ''value''"));
339        descriptionText.appendItem(tr("<b>key=*</b> - key ''key'' with any value. Try also <b>*=value</b>, <b>key=</b>, <b>*=*</b>, <b>*=</b>"));
340        descriptionText.appendItem(tr("<b>key:</b> - key ''key'' set to any value"));
341        descriptionText.appendItem(tr("<b>key?</b> - key ''key'' with the value ''yes'', ''true'', ''1'' or ''on''"));
342        if(Main.pref.getBoolean("expert", false))
343        {
344            descriptionText.appendItemHeader(tr("Special targets"));
345            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>type:</b>... - objects with corresponding type (<b>node</b>, <b>way</b>, <b>relation</b>)"));
346            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>user:</b>... - objects changed by user"));
347            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>user:anonymous</b> - objects changed by anonymous users"));
348            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>id:</b>... - objects with given ID (0 for new objects)"));
349            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>version:</b>... - objects with given version (0 objects without an assigned version)"));
350            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>changeset:</b>... - objects with given changeset ID (0 objects without an assigned changeset)"));
351            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>nodes:</b>... - objects with given number of nodes (<b>nodes:</b>count, <b>nodes:</b>min-max, <b>nodes:</b>min- or <b>nodes:</b>-max)"));
352            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>tags:</b>... - objects with given number of tags (<b>tags:</b>count, <b>tags:</b>min-max, <b>tags:</b>min- or <b>tags:</b>-max)"));
353            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>role:</b>... - objects with given role in a relation"));
354            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>timestamp:</b>timestamp - objects with this last modification timestamp (2009-11-12T14:51:09Z, 2009-11-12 or T14:51 ...)"));
355            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>timestamp:</b>min/max - objects with last modification within range"));
356            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>areasize:</b>... - closed ways with given area in m\u00b2 (<b>areasize:</b>min-max or <b>areasize:</b>max)"));
357            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>modified</b> - all changed objects"));
358            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>selected</b> - all selected objects"));
359            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>incomplete</b> - all incomplete objects"));
360            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>untagged</b> - all untagged objects"));
361            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>closed</b> - all closed ways (a node is not considered closed)"));
362            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>child <i>expr</i></b> - all children of objects matching the expression"));
363            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>parent <i>expr</i></b> - all parents of objects matching the expression"));
364            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>(all)indownloadedarea</b> - objects (and all its way nodes / relation members) in downloaded area"));
365            /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>(all)inview</b> - objects (and all its way nodes / relation members) in current view"));
366        }
367        /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("Use <b>|</b> or <b>OR</b> to combine with logical or"));
368        descriptionText.appendItem(tr("Use <b>\"</b> to quote operators (e.g. if key contains <b>:</b>)")
369                + "<br/>"
370                + tr("Within quoted strings the <b>\"</b> and <b>\\</b> characters need to be escaped by a preceding <b>\\</b> (e.g. <b>\\\"</b> and <b>\\\\</b>)."));
371        descriptionText.appendItem(tr("Use <b>(</b> and <b>)</b> to group expressions"));
372        descriptionText.append("</ul></html>");
373        JLabel description = new JLabel(descriptionText.toString());
374        description.setFont(description.getFont().deriveFont(Font.PLAIN));
375        right.add(description);
376    }
377
378    private static void buildHintsNew(JPanel right, HistoryComboBox hcbSearchString) {
379        right.add(new SearchKeywordRow(hcbSearchString)
380                .addTitle(tr("basic examples"))
381                .addKeyword(tr("Baker Street"), null, tr("''Baker'' and ''Street'' in any key"))
382                .addKeyword(tr("\"Baker Street\""), "\"\"", tr("''Baker Street'' in any key"))
383                , GBC.eol());
384        right.add(new SearchKeywordRow(hcbSearchString)
385                .addTitle(tr("basics"))
386                .addKeyword("<i>key</i>:<i>valuefragment</i>", null, tr("''valuefragment'' anywhere in ''key''"), "name:str matches name=Bakerstreet")
387                .addKeyword("-<i>key</i>:<i>valuefragment</i>", null, tr("''valuefragment'' nowhere in ''key''"))
388                .addKeyword("<i>key</i>=<i>value</i>", null, tr("''key'' with exactly ''value''"))
389                .addKeyword("<i>key</i>=*", null, tr("''key'' with any value"))
390                .addKeyword("*=<i>value</i>", null, tr("''value'' in any key"))
391                .addKeyword("<i>key</i>=", null, tr("matches if ''key'' exists"))
392                , GBC.eol());
393        right.add(new SearchKeywordRow(hcbSearchString)
394                .addTitle(tr("combinators"))
395                .addKeyword("<i>expr</i> <i>expr</i>", null, tr("logical and (both expressions have to be satisfied)"))
396                .addKeyword("<i>expr</i> | <i>expr</i>", "| ", tr("logical or (at least one expression has to be satisfied)"))
397                .addKeyword("<i>expr</i> OR <i>expr</i>", "OR ", tr("logical or (at least one expression has to be satisfied)"))
398                .addKeyword("-<i>expr</i>", null, tr("logical not"))
399                .addKeyword("(<i>expr</i>)", "()", tr("use parenthesis to group expressions"))
400                .addKeyword("\"key\"=\"value\"", "\"\"=\"\"", tr("to quote operators.<br>Within quoted strings the <b>\"</b> and <b>\\</b> characters need to be escaped by a preceding <b>\\</b> (e.g. <b>\\\"</b> and <b>\\\\</b>)."), "\"addr:street\"")
401                , GBC.eol());
402
403        if (Main.pref.getBoolean("expert", false)) {
404            right.add(new SearchKeywordRow(hcbSearchString)
405                .addTitle(tr("objects"))
406                .addKeyword("type:node", "type:node ", tr("all ways"))
407                .addKeyword("type:way", "type:way ", tr("all ways"))
408                .addKeyword("type:relation", "type:relation ", tr("all relations"))
409                .addKeyword("closed", "closed ", tr("all closed ways"))
410                , GBC.eol());
411            right.add(new SearchKeywordRow(hcbSearchString)
412                .addTitle(tr("metadata"))
413                .addKeyword("user:", "user:", tr("objects changed by user", "user:anonymous"))
414                .addKeyword("id:", "id:", tr("objects with given ID"), "id:0 (new objects)")
415                .addKeyword("version:", "version:", tr("objects with given version"), "version:0 (objects without an assigned version)")
416                .addKeyword("changeset:", "changeset:", tr("objects with given changeset ID"), "changeset:0 (objects without an assigned changeset)")
417                .addKeyword("timestamp:", "timestamp:", tr("objects with last modification timestamp within range"), "timestamp:2012/", "timestamp:2008/2011-02-04T12")
418                , GBC.eol());
419            right.add(new SearchKeywordRow(hcbSearchString)
420                .addTitle(tr("properties"))
421                .addKeyword("nodes:<i>20-</i>", "nodes:", tr("objects with at least 20 nodes"))
422                .addKeyword("tags:<i>5-10</i>", "tags:", tr("objects having 5 to 10 tags"))
423                .addKeyword("role:", "role:", tr("objects with given role in a relation"))
424                .addKeyword("areasize:<i>-100</i>", "areasize:", tr("closed ways with an area of 100 m\u00b2"))
425                , GBC.eol());
426            right.add(new SearchKeywordRow(hcbSearchString)
427                .addTitle(tr("state"))
428                .addKeyword("modified", "modified ", tr("all modified objects"))
429                .addKeyword("new", "new ", tr("all new objects"))
430                .addKeyword("selected", "selected ", tr("all selected objects"))
431                .addKeyword("incomplete", "incomplete ", tr("all incomplete objects"))
432                , GBC.eol());
433            right.add(new SearchKeywordRow(hcbSearchString)
434                .addTitle(tr("relations"))
435                .addKeyword("child <i>expr</i>", "child ", tr("all children of objects matching the expression"), "child building")
436                .addKeyword("parent <i>expr</i>", "parent ", tr("all parents of objects matching the expression"), "parent bus_stop")
437                , GBC.eol());
438            right.add(new SearchKeywordRow(hcbSearchString)
439                .addTitle(tr("view"))
440                .addKeyword("inview", "inview ", tr("objects in current view"))
441                .addKeyword("allinview", "allinview ", tr("objects (and all its way nodes / relation members) in current view"))
442                .addKeyword("indownloadedarea", "indownloadedarea ", tr("objects in downloaded area"))
443                .addKeyword("allindownloadedarea", "allindownloadedarea ", tr("objects (and all its way nodes / relation members) in downloaded area"))
444                , GBC.eol());
445        }
446    }
447
448    /**
449     * Launches the dialog for specifying search criteria and runs
450     * a search
451     */
452    public static void search() {
453        SearchSetting se = showSearchDialog(lastSearch);
454        if(se != null) {
455            searchWithHistory(se);
456        }
457    }
458
459    /**
460     * Adds the search specified by the settings in <code>s</code> to the
461     * search history and performs the search.
462     *
463     * @param s
464     */
465    public static void searchWithHistory(SearchSetting s) {
466        saveToHistory(s);
467        lastSearch = new SearchSetting(s);
468        search(s);
469    }
470
471    public static void searchWithoutHistory(SearchSetting s) {
472        lastSearch = new SearchSetting(s);
473        search(s);
474    }
475
476    public interface Function{
477        public Boolean isSomething(OsmPrimitive o);
478    }
479
480    public static int getSelection(SearchSetting s, Collection<OsmPrimitive> sel, Function f) {
481        int foundMatches = 0;
482        try {
483            String searchText = s.text;
484            SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch);
485
486            if (s.mode == SearchMode.replace) {
487                sel.clear();
488            }
489
490            Collection<OsmPrimitive> all;
491            if(s.allElements) {
492                all = Main.main.getCurrentDataSet().allPrimitives();
493            } else {
494                all = Main.main.getCurrentDataSet().allNonDeletedCompletePrimitives();
495            }
496            for (OsmPrimitive osm : all) {
497                if (s.mode == SearchMode.replace) {
498                    if (matcher.match(osm)) {
499                        sel.add(osm);
500                        ++foundMatches;
501                    }
502                } else if (s.mode == SearchMode.add && !f.isSomething(osm) && matcher.match(osm)) {
503                    sel.add(osm);
504                    ++foundMatches;
505                } else if (s.mode == SearchMode.remove && f.isSomething(osm) && matcher.match(osm)) {
506                    sel.remove(osm);
507                    ++foundMatches;
508                } else if (s.mode == SearchMode.in_selection &&  f.isSomething(osm)&& !matcher.match(osm)) {
509                    sel.remove(osm);
510                    ++foundMatches;
511                }
512            }
513        } catch (SearchCompiler.ParseError e) {
514            JOptionPane.showMessageDialog(
515                    Main.parent,
516                    e.getMessage(),
517                    tr("Error"),
518                    JOptionPane.ERROR_MESSAGE
519
520            );
521        }
522        return foundMatches;
523    }
524
525    /**
526     * Version of getSelection that is customized for filter, but should
527     * also work in other context.
528     *
529     * @param s the search settings
530     * @param all the collection of all the primitives that should be considered
531     * @param p the property that should be set/unset if something is found
532     */
533    public static void getSelection(SearchSetting s, Collection<OsmPrimitive> all, Property<OsmPrimitive, Boolean> p) {
534        try {
535            String searchText = s.text;
536            if (s instanceof Filter && ((Filter)s).inverted) {
537                searchText = String.format("-(%s)", searchText);
538            }
539            SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch);
540
541            for (OsmPrimitive osm : all) {
542                if (s.mode == SearchMode.replace) {
543                    if (matcher.match(osm)) {
544                        p.set(osm, true);
545                    } else {
546                        p.set(osm, false);
547                    }
548                } else if (s.mode == SearchMode.add && !p.get(osm) && matcher.match(osm)) {
549                    p.set(osm, true);
550                } else if (s.mode == SearchMode.remove && p.get(osm) && matcher.match(osm)) {
551                    p.set(osm, false);
552                } else if (s.mode == SearchMode.in_selection && p.get(osm) && !matcher.match(osm)) {
553                    p.set(osm, false);
554                }
555            }
556        } catch (SearchCompiler.ParseError e) {
557            JOptionPane.showMessageDialog(
558                    Main.parent,
559                    e.getMessage(),
560                    tr("Error"),
561                    JOptionPane.ERROR_MESSAGE
562
563            );
564        }
565    }
566
567    public static void search(String search, SearchMode mode) {
568        search(new SearchSetting(search, mode, false, false, false));
569    }
570
571    public static void search(SearchSetting s) {
572        // FIXME: This is confusing. The GUI says nothing about loading primitives from an URL. We'd like to *search*
573        // for URLs in the current data set.
574        // Disabling until a better solution is in place
575        //
576        //        if (search.startsWith("http://") || search.startsWith("ftp://") || search.startsWith("https://")
577        //                || search.startsWith("file:/")) {
578        //            SelectionWebsiteLoader loader = new SelectionWebsiteLoader(search, mode);
579        //            if (loader.url != null && loader.url.getHost() != null) {
580        //                Main.worker.execute(loader);
581        //                return;
582        //            }
583        //        }
584
585        final DataSet ds = Main.main.getCurrentDataSet();
586        Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(ds.getSelected());
587        int foundMatches = getSelection(s, sel, new Function(){
588            public Boolean isSomething(OsmPrimitive o){
589                return ds.isSelected(o);
590            }
591        });
592        ds.setSelected(sel);
593        if (foundMatches == 0) {
594            String msg = null;
595            if (s.mode == SearchMode.replace) {
596                msg = tr("No match found for ''{0}''", s.text);
597            } else if (s.mode == SearchMode.add) {
598                msg = tr("Nothing added to selection by searching for ''{0}''", s.text);
599            } else if (s.mode == SearchMode.remove) {
600                msg = tr("Nothing removed from selection by searching for ''{0}''", s.text);
601            } else if (s.mode == SearchMode.in_selection) {
602                msg = tr("Nothing found in selection by searching for ''{0}''", s.text);
603            }
604            Main.map.statusLine.setHelpText(msg);
605            JOptionPane.showMessageDialog(
606                    Main.parent,
607                    msg,
608                    tr("Warning"),
609                    JOptionPane.WARNING_MESSAGE
610            );
611        } else {
612            Main.map.statusLine.setHelpText(tr("Found {0} matches", foundMatches));
613        }
614    }
615
616    public static class SearchSetting {
617        public String text;
618        public SearchMode mode;
619        public boolean caseSensitive;
620        public boolean regexSearch;
621        public boolean allElements;
622
623        public SearchSetting() {
624            this("", SearchMode.replace, false /* case insensitive */,
625                    false /* no regexp */, false /* only useful primitives */);
626        }
627
628        public SearchSetting(String text, SearchMode mode, boolean caseSensitive,
629                boolean regexSearch, boolean allElements) {
630            this.caseSensitive = caseSensitive;
631            this.regexSearch = regexSearch;
632            this.allElements = allElements;
633            this.mode = mode;
634            this.text = text;
635        }
636
637        public SearchSetting(SearchSetting original) {
638            this(original.text, original.mode, original.caseSensitive,
639                    original.regexSearch, original.allElements);
640        }
641
642        @Override
643        public String toString() {
644            String cs = caseSensitive ?
645                    /*case sensitive*/  trc("search", "CS") :
646                        /*case insensitive*/  trc("search", "CI");
647                    String rx = regexSearch ? (", " +
648                            /*regex search*/ trc("search", "RX")) : "";
649                    String all = allElements ? (", " +
650                            /*all elements*/ trc("search", "A")) : "";
651                    return "\"" + text + "\" (" + cs + rx + all + ", " + mode + ")";
652        }
653
654        @Override
655        public boolean equals(Object other) {
656            if(!(other instanceof SearchSetting))
657                return false;
658            SearchSetting o = (SearchSetting) other;
659            return (o.caseSensitive == this.caseSensitive
660                    && o.regexSearch == this.regexSearch
661                    && o.allElements == this.allElements
662                    && o.mode.equals(this.mode)
663                    && o.text.equals(this.text));
664        }
665
666        @Override
667        public int hashCode() {
668            return text.hashCode();
669        }
670
671        public static SearchSetting readFromString(String s) {
672            if (s.length() == 0)
673                return null;
674
675            SearchSetting result = new SearchSetting();
676
677            int index = 1;
678
679            result.mode = SearchMode.fromCode(s.charAt(0));
680            if (result.mode == null) {
681                result.mode = SearchMode.replace;
682                index = 0;
683            }
684
685            while (index < s.length()) {
686                if (s.charAt(index) == 'C') {
687                    result.caseSensitive = true;
688                } else if (s.charAt(index) == 'R') {
689                    result.regexSearch = true;
690                } else if (s.charAt(index) == 'A') {
691                    result.allElements = true;
692                } else if (s.charAt(index) == ' ') {
693                    break;
694                } else {
695                    System.out.println("Unknown char in SearchSettings: " + s);
696                    break;
697                }
698                index++;
699            }
700
701            if (index < s.length() && s.charAt(index) == ' ') {
702                index++;
703            }
704
705            result.text = s.substring(index);
706
707            return result;
708        }
709
710        public String writeToString() {
711            if (text == null || text.length() == 0)
712                return "";
713
714            StringBuilder result = new StringBuilder();
715            result.append(mode.getCode());
716            if (caseSensitive) {
717                result.append('C');
718            }
719            if (regexSearch) {
720                result.append('R');
721            }
722            if (allElements) {
723                result.append('A');
724            }
725            result.append(' ');
726            result.append(text);
727            return result.toString();
728        }
729    }
730
731    /**
732     * Refreshes the enabled state
733     *
734     */
735    @Override
736    protected void updateEnabledState() {
737        setEnabled(getEditLayer() != null);
738    }
739
740    public List<ActionParameter<?>> getActionParameters() {
741        return Collections.<ActionParameter<?>>singletonList(new SearchSettingsActionParameter(SEARCH_EXPRESSION));
742    }
743
744    public static String escapeStringForSearch(String s) {
745        return s.replace("\\", "\\\\").replace("\"", "\\\"");
746    }
747}
Note: See TracBrowser for help on using the repository browser.