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

Last change on this file since 9079 was 9079, checked in by Don-vip, 9 years ago

update to CheckStyle 6.13

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