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

Last change on this file since 8198 was 8198, checked in by simon04, 9 years ago

fix #11343 - Search: add waylength keyword to test the length of ways in meters

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