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

Last change on this file since 5904 was 5889, checked in by Don-vip, 11 years ago

fix potential bugs detected by FindBugs

  • Property svn:eol-style set to native
File size: 34.2 KB
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.text.BadLocationException;
33
34import org.openstreetmap.josm.Main;
35import org.openstreetmap.josm.actions.ActionParameter;
36import org.openstreetmap.josm.actions.JosmAction;
37import org.openstreetmap.josm.actions.ParameterizedAction;
38import org.openstreetmap.josm.actions.ActionParameter.SearchSettingsActionParameter;
39import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError;
40import org.openstreetmap.josm.data.osm.DataSet;
41import org.openstreetmap.josm.data.osm.Filter;
42import org.openstreetmap.josm.data.osm.OsmPrimitive;
43import org.openstreetmap.josm.gui.ExtendedDialog;
44import org.openstreetmap.josm.gui.preferences.ToolbarPreferences;
45import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser;
46import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
47import org.openstreetmap.josm.tools.GBC;
48import org.openstreetmap.josm.tools.Predicate;
49import org.openstreetmap.josm.tools.Property;
50import org.openstreetmap.josm.tools.Shortcut;
51import org.openstreetmap.josm.tools.Utils;
52import org.openstreetmap.josm.gui.widgets.JosmTextField;
53
54
55public class SearchAction extends JosmAction implements ParameterizedAction {
56
57 public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15;
58
59 private static final String SEARCH_EXPRESSION = "searchExpression";
60
61 public static enum SearchMode {
62 replace('R'), add('A'), remove('D'), in_selection('S');
63
64 private final char code;
65
66 SearchMode(char code) {
67 this.code = code;
68 }
69
70 public char getCode() {
71 return code;
72 }
73
74 public static SearchMode fromCode(char code) {
75 for (SearchMode mode: values()) {
76 if (mode.getCode() == code)
77 return mode;
78 }
79 return null;
80 }
81 }
82
83 private static final LinkedList<SearchSetting> searchHistory = new LinkedList<SearchSetting>();
84 static {
85 for (String s: Main.pref.getCollection("search.history", Collections.<String>emptyList())) {
86 SearchSetting ss = SearchSetting.readFromString(s);
87 if (ss != null) {
88 searchHistory.add(ss);
89 }
90 }
91 }
92
93 public static Collection<SearchSetting> getSearchHistory() {
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 JosmTextField tf = (JosmTextField) 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 .addKeyword("untagged", "untagged ", tr("object without useful tags"))
411 , GBC.eol());
412 right.add(new SearchKeywordRow(hcbSearchString)
413 .addTitle(tr("metadata"))
414 .addKeyword("user:", "user:", tr("objects changed by user", "user:anonymous"))
415 .addKeyword("id:", "id:", tr("objects with given ID"), "id:0 (new objects)")
416 .addKeyword("version:", "version:", tr("objects with given version"), "version:0 (objects without an assigned version)")
417 .addKeyword("changeset:", "changeset:", tr("objects with given changeset ID"), "changeset:0 (objects without an assigned changeset)")
418 .addKeyword("timestamp:", "timestamp:", tr("objects with last modification timestamp within range"), "timestamp:2012/", "timestamp:2008/2011-02-04T12")
419 , GBC.eol());
420 right.add(new SearchKeywordRow(hcbSearchString)
421 .addTitle(tr("properties"))
422 .addKeyword("nodes:<i>20-</i>", "nodes:", tr("objects with at least 20 nodes"))
423 .addKeyword("tags:<i>5-10</i>", "tags:", tr("objects having 5 to 10 tags"))
424 .addKeyword("role:", "role:", tr("objects with given role in a relation"))
425 .addKeyword("areasize:<i>-100</i>", "areasize:", tr("closed ways with an area of 100 m\u00b2"))
426 , GBC.eol());
427 right.add(new SearchKeywordRow(hcbSearchString)
428 .addTitle(tr("state"))
429 .addKeyword("modified", "modified ", tr("all modified objects"))
430 .addKeyword("new", "new ", tr("all new objects"))
431 .addKeyword("selected", "selected ", tr("all selected objects"))
432 .addKeyword("incomplete", "incomplete ", tr("all incomplete objects"))
433 , GBC.eol());
434 right.add(new SearchKeywordRow(hcbSearchString)
435 .addTitle(tr("related objects"))
436 .addKeyword("child <i>expr</i>", "child ", tr("all children of objects matching the expression"), "child building")
437 .addKeyword("parent <i>expr</i>", "parent ", tr("all parents of objects matching the expression"), "parent bus_stop")
438 .addKeyword("nth:<i>7</i>", "nth: ", tr("n-th member of relation and/or n-th node of way"), "nth:5 (child type:relation)")
439 .addKeyword("nth%:<i>7</i>", "nth%: ", tr("every n-th member of relation and/or every n-th node of way"), "nth%:100 (child waterway)")
440 , GBC.eol());
441 right.add(new SearchKeywordRow(hcbSearchString)
442 .addTitle(tr("view"))
443 .addKeyword("inview", "inview ", tr("objects in current view"))
444 .addKeyword("allinview", "allinview ", tr("objects (and all its way nodes / relation members) in current view"))
445 .addKeyword("indownloadedarea", "indownloadedarea ", tr("objects in downloaded area"))
446 .addKeyword("allindownloadedarea", "allindownloadedarea ", tr("objects (and all its way nodes / relation members) in downloaded area"))
447 , GBC.eol());
448 }
449 }
450
451 /**
452 * Launches the dialog for specifying search criteria and runs
453 * a search
454 */
455 public static void search() {
456 SearchSetting se = showSearchDialog(lastSearch);
457 if(se != null) {
458 searchWithHistory(se);
459 }
460 }
461
462 /**
463 * Adds the search specified by the settings in <code>s</code> to the
464 * search history and performs the search.
465 *
466 * @param s
467 */
468 public static void searchWithHistory(SearchSetting s) {
469 saveToHistory(s);
470 lastSearch = new SearchSetting(s);
471 search(s);
472 }
473
474 public static void searchWithoutHistory(SearchSetting s) {
475 lastSearch = new SearchSetting(s);
476 search(s);
477 }
478
479 public static int getSelection(SearchSetting s, Collection<OsmPrimitive> sel, Predicate<OsmPrimitive> p) {
480 int foundMatches = 0;
481 try {
482 String searchText = s.text;
483 SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch);
484
485 if (s.mode == SearchMode.replace) {
486 sel.clear();
487 }
488
489 Collection<OsmPrimitive> all;
490 if(s.allElements) {
491 all = Main.main.getCurrentDataSet().allPrimitives();
492 } else {
493 all = Main.main.getCurrentDataSet().allNonDeletedCompletePrimitives();
494 }
495 for (OsmPrimitive osm : all) {
496 if (s.mode == SearchMode.replace) {
497 if (matcher.match(osm)) {
498 sel.add(osm);
499 ++foundMatches;
500 }
501 } else if (s.mode == SearchMode.add && !p.evaluate(osm) && matcher.match(osm)) {
502 sel.add(osm);
503 ++foundMatches;
504 } else if (s.mode == SearchMode.remove && p.evaluate(osm) && matcher.match(osm)) {
505 sel.remove(osm);
506 ++foundMatches;
507 } else if (s.mode == SearchMode.in_selection && p.evaluate(osm) && !matcher.match(osm)) {
508 sel.remove(osm);
509 ++foundMatches;
510 }
511 }
512 } catch (SearchCompiler.ParseError e) {
513 JOptionPane.showMessageDialog(
514 Main.parent,
515 e.getMessage(),
516 tr("Error"),
517 JOptionPane.ERROR_MESSAGE
518
519 );
520 }
521 return foundMatches;
522 }
523
524 /**
525 * Version of getSelection that is customized for filter, but should
526 * also work in other context.
527 *
528 * @param s the search settings
529 * @param all the collection of all the primitives that should be considered
530 * @param p the property that should be set/unset if something is found
531 */
532 public static void getSelection(SearchSetting s, Collection<OsmPrimitive> all, Property<OsmPrimitive, Boolean> p) {
533 try {
534 String searchText = s.text;
535 if (s instanceof Filter && ((Filter)s).inverted) {
536 searchText = String.format("-(%s)", searchText);
537 }
538 SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch);
539
540 for (OsmPrimitive osm : all) {
541 if (s.mode == SearchMode.replace) {
542 if (matcher.match(osm)) {
543 p.set(osm, true);
544 } else {
545 p.set(osm, false);
546 }
547 } else if (s.mode == SearchMode.add && !p.get(osm) && matcher.match(osm)) {
548 p.set(osm, true);
549 } else if (s.mode == SearchMode.remove && p.get(osm) && matcher.match(osm)) {
550 p.set(osm, false);
551 } else if (s.mode == SearchMode.in_selection && p.get(osm) && !matcher.match(osm)) {
552 p.set(osm, false);
553 }
554 }
555 } catch (SearchCompiler.ParseError e) {
556 JOptionPane.showMessageDialog(
557 Main.parent,
558 e.getMessage(),
559 tr("Error"),
560 JOptionPane.ERROR_MESSAGE
561
562 );
563 }
564 }
565
566 public static void search(String search, SearchMode mode) {
567 search(new SearchSetting(search, mode, false, false, false));
568 }
569
570 public static void search(SearchSetting s) {
571
572 final DataSet ds = Main.main.getCurrentDataSet();
573 Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(ds.getAllSelected());
574 int foundMatches = getSelection(s, sel, new Predicate<OsmPrimitive>(){
575 @Override
576 public boolean evaluate(OsmPrimitive o){
577 return ds.isSelected(o);
578 }
579 });
580 ds.setSelected(sel);
581 if (foundMatches == 0) {
582 String msg = null;
583 if (s.mode == SearchMode.replace) {
584 msg = tr("No match found for ''{0}''", s.text);
585 } else if (s.mode == SearchMode.add) {
586 msg = tr("Nothing added to selection by searching for ''{0}''", s.text);
587 } else if (s.mode == SearchMode.remove) {
588 msg = tr("Nothing removed from selection by searching for ''{0}''", s.text);
589 } else if (s.mode == SearchMode.in_selection) {
590 msg = tr("Nothing found in selection by searching for ''{0}''", s.text);
591 }
592 Main.map.statusLine.setHelpText(msg);
593 JOptionPane.showMessageDialog(
594 Main.parent,
595 msg,
596 tr("Warning"),
597 JOptionPane.WARNING_MESSAGE
598 );
599 } else {
600 Main.map.statusLine.setHelpText(tr("Found {0} matches", foundMatches));
601 }
602 }
603
604 public static class SearchSetting {
605 public String text;
606 public SearchMode mode;
607 public boolean caseSensitive;
608 public boolean regexSearch;
609 public boolean allElements;
610
611 public SearchSetting() {
612 this("", SearchMode.replace, false /* case insensitive */,
613 false /* no regexp */, false /* only useful primitives */);
614 }
615
616 public SearchSetting(String text, SearchMode mode, boolean caseSensitive,
617 boolean regexSearch, boolean allElements) {
618 this.caseSensitive = caseSensitive;
619 this.regexSearch = regexSearch;
620 this.allElements = allElements;
621 this.mode = mode;
622 this.text = text;
623 }
624
625 public SearchSetting(SearchSetting original) {
626 this(original.text, original.mode, original.caseSensitive,
627 original.regexSearch, original.allElements);
628 }
629
630 @Override
631 public String toString() {
632 String cs = caseSensitive ?
633 /*case sensitive*/ trc("search", "CS") :
634 /*case insensitive*/ trc("search", "CI");
635 String rx = regexSearch ? (", " +
636 /*regex search*/ trc("search", "RX")) : "";
637 String all = allElements ? (", " +
638 /*all elements*/ trc("search", "A")) : "";
639 return "\"" + text + "\" (" + cs + rx + all + ", " + mode + ")";
640 }
641
642 @Override
643 public boolean equals(Object other) {
644 if(!(other instanceof SearchSetting))
645 return false;
646 SearchSetting o = (SearchSetting) other;
647 return (o.caseSensitive == this.caseSensitive
648 && o.regexSearch == this.regexSearch
649 && o.allElements == this.allElements
650 && o.mode.equals(this.mode)
651 && o.text.equals(this.text));
652 }
653
654 @Override
655 public int hashCode() {
656 return text.hashCode();
657 }
658
659 public static SearchSetting readFromString(String s) {
660 if (s.length() == 0)
661 return null;
662
663 SearchSetting result = new SearchSetting();
664
665 int index = 1;
666
667 result.mode = SearchMode.fromCode(s.charAt(0));
668 if (result.mode == null) {
669 result.mode = SearchMode.replace;
670 index = 0;
671 }
672
673 while (index < s.length()) {
674 if (s.charAt(index) == 'C') {
675 result.caseSensitive = true;
676 } else if (s.charAt(index) == 'R') {
677 result.regexSearch = true;
678 } else if (s.charAt(index) == 'A') {
679 result.allElements = true;
680 } else if (s.charAt(index) == ' ') {
681 break;
682 } else {
683 System.out.println("Unknown char in SearchSettings: " + s);
684 break;
685 }
686 index++;
687 }
688
689 if (index < s.length() && s.charAt(index) == ' ') {
690 index++;
691 }
692
693 result.text = s.substring(index);
694
695 return result;
696 }
697
698 public String writeToString() {
699 if (text == null || text.length() == 0)
700 return "";
701
702 StringBuilder result = new StringBuilder();
703 result.append(mode.getCode());
704 if (caseSensitive) {
705 result.append('C');
706 }
707 if (regexSearch) {
708 result.append('R');
709 }
710 if (allElements) {
711 result.append('A');
712 }
713 result.append(' ');
714 result.append(text);
715 return result.toString();
716 }
717 }
718
719 /**
720 * Refreshes the enabled state
721 *
722 */
723 @Override
724 protected void updateEnabledState() {
725 setEnabled(getEditLayer() != null);
726 }
727
728 public List<ActionParameter<?>> getActionParameters() {
729 return Collections.<ActionParameter<?>>singletonList(new SearchSettingsActionParameter(SEARCH_EXPRESSION));
730 }
731
732 public static String escapeStringForSearch(String s) {
733 return s.replace("\\", "\\\\").replace("\"", "\\\"");
734 }
735}
Note: See TracBrowser for help on using the repository browser.