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

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

checkstyle

  • Property svn:eol-style set to native
File size: 31.5 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.HistoryComboBox;
52import org.openstreetmap.josm.tools.GBC;
53import org.openstreetmap.josm.tools.Predicate;
54import org.openstreetmap.josm.tools.Shortcut;
55import org.openstreetmap.josm.tools.Utils;
56
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 hcbSearchString.setText(initialValues.text);
231 hcbSearchString.setToolTipText(tr("Enter the search expression"));
232 // we have to reverse the history, because ComboBoxHistory will reverse it again in addElement()
233 //
234 List<String> searchExpressionHistory = getSearchExpressionHistory();
235 Collections.reverse(searchExpressionHistory);
236 hcbSearchString.setPossibleItems(searchExpressionHistory);
237 hcbSearchString.setPreferredSize(new Dimension(40, hcbSearchString.getPreferredSize().height));
238 label.setLabelFor(hcbSearchString);
239
240 JRadioButton replace = new JRadioButton(tr("replace selection"), initialValues.mode == SearchMode.replace);
241 JRadioButton add = new JRadioButton(tr("add to selection"), initialValues.mode == SearchMode.add);
242 JRadioButton remove = new JRadioButton(tr("remove from selection"), initialValues.mode == SearchMode.remove);
243 JRadioButton in_selection = new JRadioButton(tr("find in selection"), initialValues.mode == SearchMode.in_selection);
244 ButtonGroup bg = new ButtonGroup();
245 bg.add(replace);
246 bg.add(add);
247 bg.add(remove);
248 bg.add(in_selection);
249
250 final JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
251 JCheckBox allElements = new JCheckBox(tr("all objects"), initialValues.allElements);
252 allElements.setToolTipText(tr("Also include incomplete and deleted objects in search."));
253 final JRadioButton standardSearch = new JRadioButton(tr("standard"), !initialValues.regexSearch && !initialValues.mapCSSSearch);
254 final JRadioButton regexSearch = new JRadioButton(tr("regular expression"), initialValues.regexSearch);
255 final JRadioButton mapCSSSearch = new JRadioButton(tr("MapCSS selector"), initialValues.mapCSSSearch);
256 final JCheckBox addOnToolbar = new JCheckBox(tr("add toolbar button"), false);
257 final ButtonGroup bg2 = new ButtonGroup();
258 bg2.add(standardSearch);
259 bg2.add(regexSearch);
260 bg2.add(mapCSSSearch);
261
262 JPanel top = new JPanel(new GridBagLayout());
263 top.add(label, GBC.std().insets(0, 0, 5, 0));
264 top.add(hcbSearchString, GBC.eol().fill(GBC.HORIZONTAL));
265 JPanel left = new JPanel(new GridBagLayout());
266 left.add(replace, GBC.eol());
267 left.add(add, GBC.eol());
268 left.add(remove, GBC.eol());
269 left.add(in_selection, GBC.eop());
270 left.add(caseSensitive, GBC.eol());
271 if (Main.pref.getBoolean("expert", false)) {
272 left.add(allElements, GBC.eol());
273 left.add(addOnToolbar, GBC.eop());
274 left.add(standardSearch, GBC.eol());
275 left.add(regexSearch, GBC.eol());
276 left.add(mapCSSSearch, GBC.eol());
277 }
278
279 final JPanel right;
280 right = new JPanel(new GridBagLayout());
281 buildHints(right, hcbSearchString);
282
283 final JPanel p = new JPanel(new GridBagLayout());
284 p.add(top, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 0));
285 p.add(left, GBC.std().anchor(GBC.NORTH).insets(5, 10, 10, 0));
286 p.add(right, GBC.eol());
287 ExtendedDialog dialog = new ExtendedDialog(
288 Main.parent,
289 initialValues instanceof Filter ? tr("Filter") : tr("Search"),
290 new String[] {
291 initialValues instanceof Filter ? tr("Submit filter") : tr("Start Search"),
292 tr("Cancel")}
293 ) {
294 @Override
295 protected void buttonAction(int buttonIndex, ActionEvent evt) {
296 if (buttonIndex == 0) {
297 try {
298 SearchSetting ss = new SearchSetting();
299 ss.text = hcbSearchString.getText();
300 ss.caseSensitive = caseSensitive.isSelected();
301 ss.regexSearch = regexSearch.isSelected();
302 ss.mapCSSSearch = mapCSSSearch.isSelected();
303 SearchCompiler.compile(ss);
304 super.buttonAction(buttonIndex, evt);
305 } catch (ParseError e) {
306 JOptionPane.showMessageDialog(
307 Main.parent,
308 tr("Search expression is not valid: \n\n {0}", e.getMessage()),
309 tr("Invalid search expression"),
310 JOptionPane.ERROR_MESSAGE);
311 }
312 } else {
313 super.buttonAction(buttonIndex, evt);
314 }
315 }
316 };
317 dialog.setButtonIcons(new String[] {"dialogs/search", "cancel"});
318 dialog.configureContextsensitiveHelp("/Action/Search", true /* show help button */);
319 dialog.setContent(p);
320 dialog.showDialog();
321 int result = dialog.getValue();
322
323 if (result != 1) return null;
324
325 // User pressed OK - let's perform the search
326 SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace
327 : (add.isSelected() ? SearchAction.SearchMode.add
328 : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection));
329 initialValues.text = hcbSearchString.getText();
330 initialValues.mode = mode;
331 initialValues.caseSensitive = caseSensitive.isSelected();
332 initialValues.allElements = allElements.isSelected();
333 initialValues.regexSearch = regexSearch.isSelected();
334 initialValues.mapCSSSearch = mapCSSSearch.isSelected();
335
336 if (addOnToolbar.isSelected()) {
337 ToolbarPreferences.ActionDefinition aDef =
338 new ToolbarPreferences.ActionDefinition(Main.main.menu.search);
339 aDef.getParameters().put(SEARCH_EXPRESSION, initialValues);
340 // Display search expression as tooltip instead of generic one
341 aDef.setName(Utils.shortenString(initialValues.text, MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY));
342 // parametrized action definition is now composed
343 ActionParser actionParser = new ToolbarPreferences.ActionParser(null);
344 String res = actionParser.saveAction(aDef);
345
346 // add custom search button to toolbar preferences
347 Main.toolbar.addCustomButton(res, -1, false);
348 }
349 return initialValues;
350 }
351
352 private static void buildHints(JPanel right, HistoryComboBox hcbSearchString) {
353 right.add(new SearchKeywordRow(hcbSearchString)
354 .addTitle(tr("basic examples"))
355 .addKeyword(tr("Baker Street"), null, tr("''Baker'' and ''Street'' in any key"))
356 .addKeyword(tr("\"Baker Street\""), "\"\"", tr("''Baker Street'' in any key")),
357 GBC.eol());
358 right.add(new SearchKeywordRow(hcbSearchString)
359 .addTitle(tr("basics"))
360 .addKeyword("<i>key</i>:<i>valuefragment</i>", null,
361 tr("''valuefragment'' anywhere in ''key''"), "name:str matches name=Bakerstreet")
362 .addKeyword("-<i>key</i>:<i>valuefragment</i>", null, tr("''valuefragment'' nowhere in ''key''"))
363 .addKeyword("<i>key</i>=<i>value</i>", null, tr("''key'' with exactly ''value''"))
364 .addKeyword("<i>key</i>=*", null, tr("''key'' with any value"))
365 .addKeyword("*=<i>value</i>", null, tr("''value'' in any key"))
366 .addKeyword("<i>key</i>=", null, tr("matches if ''key'' exists"))
367 .addKeyword("<i>key</i>><i>value</i>", null, tr("matches if ''key'' is greater than ''value'' (analogously, less than)"))
368 .addKeyword("\"key\"=\"value\"", "\"\"=\"\"",
369 tr("to quote operators.<br>Within quoted strings the <b>\"</b> and <b>\\</b> characters need to be escaped " +
370 "by a preceding <b>\\</b> (e.g. <b>\\\"</b> and <b>\\\\</b>)."),
371 "\"addr:street\""),
372 GBC.eol());
373 right.add(new SearchKeywordRow(hcbSearchString)
374 .addTitle(tr("combinators"))
375 .addKeyword("<i>expr</i> <i>expr</i>", null, tr("logical and (both expressions have to be satisfied)"))
376 .addKeyword("<i>expr</i> | <i>expr</i>", "| ", tr("logical or (at least one expression has to be satisfied)"))
377 .addKeyword("<i>expr</i> OR <i>expr</i>", "OR ", tr("logical or (at least one expression has to be satisfied)"))
378 .addKeyword("-<i>expr</i>", null, tr("logical not"))
379 .addKeyword("(<i>expr</i>)", "()", tr("use parenthesis to group expressions")),
380 GBC.eol());
381
382 if (Main.pref.getBoolean("expert", false)) {
383 right.add(new SearchKeywordRow(hcbSearchString)
384 .addTitle(tr("objects"))
385 .addKeyword("type:node", "type:node ", tr("all ways"))
386 .addKeyword("type:way", "type:way ", tr("all ways"))
387 .addKeyword("type:relation", "type:relation ", tr("all relations"))
388 .addKeyword("closed", "closed ", tr("all closed ways"))
389 .addKeyword("untagged", "untagged ", tr("object without useful tags")),
390 GBC.eol());
391 right.add(new SearchKeywordRow(hcbSearchString)
392 .addTitle(tr("metadata"))
393 .addKeyword("user:", "user:", tr("objects changed by user", "user:anonymous"))
394 .addKeyword("id:", "id:", tr("objects with given ID"), "id:0 (new objects)")
395 .addKeyword("version:", "version:", tr("objects with given version"), "version:0 (objects without an assigned version)")
396 .addKeyword("changeset:", "changeset:", tr("objects with given changeset ID"),
397 "changeset:0 (objects without an assigned changeset)")
398 .addKeyword("timestamp:", "timestamp:", tr("objects with last modification timestamp within range"), "timestamp:2012/",
399 "timestamp:2008/2011-02-04T12"),
400 GBC.eol());
401 right.add(new SearchKeywordRow(hcbSearchString)
402 .addTitle(tr("properties"))
403 .addKeyword("nodes:<i>20-</i>", "nodes:", tr("ways with at least 20 nodes, or relations containing at least 20 nodes"))
404 .addKeyword("ways:<i>3-</i>", "ways:", tr("nodes with at least 3 referring ways, or relations containing at least 3 ways"))
405 .addKeyword("tags:<i>5-10</i>", "tags:", tr("objects having 5 to 10 tags"))
406 .addKeyword("role:", "role:", tr("objects with given role in a relation"))
407 .addKeyword("areasize:<i>-100</i>", "areasize:", tr("closed ways with an area of 100 m\u00b2"))
408 .addKeyword("waylength:<i>200-</i>", "waylength:", tr("ways with a length of 200 m or more")),
409 GBC.eol());
410 right.add(new SearchKeywordRow(hcbSearchString)
411 .addTitle(tr("state"))
412 .addKeyword("modified", "modified ", tr("all modified objects"))
413 .addKeyword("new", "new ", tr("all new objects"))
414 .addKeyword("selected", "selected ", tr("all selected objects"))
415 .addKeyword("incomplete", "incomplete ", tr("all incomplete objects")),
416 GBC.eol());
417 right.add(new SearchKeywordRow(hcbSearchString)
418 .addTitle(tr("related objects"))
419 .addKeyword("child <i>expr</i>", "child ", tr("all children of objects matching the expression"), "child building")
420 .addKeyword("parent <i>expr</i>", "parent ", tr("all parents of objects matching the expression"), "parent bus_stop")
421 .addKeyword("hasRole:<i>stop</i>", "hasRole:", tr("relation containing a member of role <i>stop</i>"))
422 .addKeyword("role:<i>stop</i>", "role:", tr("objects being part of a relation as role <i>stop</i>"))
423 .addKeyword("nth:<i>7</i>", "nth:",
424 tr("n-th member of relation and/or n-th node of way"), "nth:5 (child type:relation)", "nth:-1")
425 .addKeyword("nth%:<i>7</i>", "nth%:",
426 tr("every n-th member of relation and/or every n-th node of way"), "nth%:100 (child waterway)"),
427 GBC.eol());
428 right.add(new SearchKeywordRow(hcbSearchString)
429 .addTitle(tr("view"))
430 .addKeyword("inview", "inview ", tr("objects in current view"))
431 .addKeyword("allinview", "allinview ", tr("objects (and all its way nodes / relation members) in current view"))
432 .addKeyword("indownloadedarea", "indownloadedarea ", tr("objects in downloaded area"))
433 .addKeyword("allindownloadedarea", "allindownloadedarea ",
434 tr("objects (and all its way nodes / relation members) in downloaded area")),
435 GBC.eol());
436 }
437 }
438
439 /**
440 * Launches the dialog for specifying search criteria and runs a search
441 */
442 public static void search() {
443 SearchSetting se = showSearchDialog(lastSearch);
444 if (se != null) {
445 searchWithHistory(se);
446 }
447 }
448
449 /**
450 * Adds the search specified by the settings in <code>s</code> to the
451 * search history and performs the search.
452 *
453 * @param s search settings
454 */
455 public static void searchWithHistory(SearchSetting s) {
456 saveToHistory(s);
457 lastSearch = new SearchSetting(s);
458 search(s);
459 }
460
461 /**
462 * Performs the search specified by the settings in <code>s</code> without saving it to search history.
463 *
464 * @param s search settings
465 */
466 public static void searchWithoutHistory(SearchSetting s) {
467 lastSearch = new SearchSetting(s);
468 search(s);
469 }
470
471 /**
472 * Performs the search specified by the search string {@code search} and the search mode {@code mode}.
473 *
474 * @param search the search string to use
475 * @param mode the search mode to use
476 */
477 public static void search(String search, SearchMode mode) {
478 final SearchSetting searchSetting = new SearchSetting();
479 searchSetting.text = search;
480 searchSetting.mode = mode;
481 search(searchSetting);
482 }
483
484 static void search(SearchSetting s) {
485 SearchTask.newSearchTask(s).run();
486 }
487
488 static final class SearchTask extends PleaseWaitRunnable {
489 private final DataSet ds;
490 private final SearchSetting setting;
491 private final Collection<OsmPrimitive> selection;
492 private final Predicate<OsmPrimitive> predicate;
493 private boolean canceled;
494 private int foundMatches;
495
496 private SearchTask(DataSet ds, SearchSetting setting, Collection<OsmPrimitive> selection, Predicate<OsmPrimitive> predicate) {
497 super(tr("Searching"));
498 this.ds = ds;
499 this.setting = setting;
500 this.selection = selection;
501 this.predicate = predicate;
502 }
503
504 static SearchTask newSearchTask(SearchSetting setting) {
505 final DataSet ds = Main.main.getCurrentDataSet();
506 final Collection<OsmPrimitive> selection = new HashSet<>(ds.getAllSelected());
507 return new SearchTask(ds, setting, selection, new Predicate<OsmPrimitive>() {
508 @Override
509 public boolean evaluate(OsmPrimitive o) {
510 return ds.isSelected(o);
511 }
512 });
513 }
514
515 @Override
516 protected void cancel() {
517 this.canceled = true;
518 }
519
520 @Override
521 protected void realRun() {
522 try {
523 foundMatches = 0;
524 SearchCompiler.Match matcher = SearchCompiler.compile(setting);
525
526 if (setting.mode == SearchMode.replace) {
527 selection.clear();
528 } else if (setting.mode == SearchMode.in_selection) {
529 foundMatches = selection.size();
530 }
531
532 Collection<OsmPrimitive> all;
533 if (setting.allElements) {
534 all = Main.main.getCurrentDataSet().allPrimitives();
535 } else {
536 all = Main.main.getCurrentDataSet().allNonDeletedCompletePrimitives();
537 }
538 final ProgressMonitor subMonitor = getProgressMonitor().createSubTaskMonitor(all.size(), false);
539 subMonitor.beginTask(trn("Searching in {0} object", "Searching in {0} objects", all.size(), all.size()));
540
541 for (OsmPrimitive osm : all) {
542 if (canceled) {
543 return;
544 }
545 if (setting.mode == SearchMode.replace) {
546 if (matcher.match(osm)) {
547 selection.add(osm);
548 ++foundMatches;
549 }
550 } else if (setting.mode == SearchMode.add && !predicate.evaluate(osm) && matcher.match(osm)) {
551 selection.add(osm);
552 ++foundMatches;
553 } else if (setting.mode == SearchMode.remove && predicate.evaluate(osm) && matcher.match(osm)) {
554 selection.remove(osm);
555 ++foundMatches;
556 } else if (setting.mode == SearchMode.in_selection && predicate.evaluate(osm) && !matcher.match(osm)) {
557 selection.remove(osm);
558 --foundMatches;
559 }
560 subMonitor.worked(1);
561 }
562 subMonitor.finishTask();
563 } catch (SearchCompiler.ParseError e) {
564 JOptionPane.showMessageDialog(
565 Main.parent,
566 e.getMessage(),
567 tr("Error"),
568 JOptionPane.ERROR_MESSAGE
569
570 );
571 }
572 }
573
574 @Override
575 protected void finish() {
576 if (canceled) {
577 return;
578 }
579 ds.setSelected(selection);
580 if (foundMatches == 0) {
581 final String msg;
582 final String text = Utils.shortenString(setting.text, MAX_LENGTH_SEARCH_EXPRESSION_DISPLAY);
583 if (setting.mode == SearchMode.replace) {
584 msg = tr("No match found for ''{0}''", text);
585 } else if (setting.mode == SearchMode.add) {
586 msg = tr("Nothing added to selection by searching for ''{0}''", text);
587 } else if (setting.mode == SearchMode.remove) {
588 msg = tr("Nothing removed from selection by searching for ''{0}''", text);
589 } else if (setting.mode == SearchMode.in_selection) {
590 msg = tr("Nothing found in selection by searching for ''{0}''", text);
591 } else {
592 msg = null;
593 }
594 Main.map.statusLine.setHelpText(msg);
595 JOptionPane.showMessageDialog(
596 Main.parent,
597 msg,
598 tr("Warning"),
599 JOptionPane.WARNING_MESSAGE
600 );
601 } else {
602 Main.map.statusLine.setHelpText(tr("Found {0} matches", foundMatches));
603 }
604 }
605 }
606
607 public static class SearchSetting {
608 public String text = "";
609 public SearchMode mode = SearchMode.replace;
610 public boolean caseSensitive;
611 public boolean regexSearch;
612 public boolean mapCSSSearch;
613 public boolean allElements;
614
615 /**
616 * Constructs a new {@code SearchSetting}.
617 */
618 public SearchSetting() {
619 }
620
621 public SearchSetting(SearchSetting original) {
622 text = original.text;
623 mode = original.mode;
624 caseSensitive = original.caseSensitive;
625 regexSearch = original.regexSearch;
626 mapCSSSearch = original.mapCSSSearch;
627 allElements = 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 css = mapCSSSearch ? ", " +
638 /*MapCSS search*/ trc("search", "CSS") : "";
639 String all = allElements ? ", " +
640 /*all elements*/ trc("search", "A") : "";
641 return '"' + text + "\" (" + cs + rx + css + all + ", " + mode + ')';
642 }
643
644 @Override
645 public boolean equals(Object other) {
646 if (!(other instanceof SearchSetting))
647 return false;
648 SearchSetting o = (SearchSetting) other;
649 return o.caseSensitive == this.caseSensitive
650 && o.regexSearch == this.regexSearch
651 && o.mapCSSSearch == this.mapCSSSearch
652 && o.allElements == this.allElements
653 && o.mode.equals(this.mode)
654 && o.text.equals(this.text);
655 }
656
657 @Override
658 public int hashCode() {
659 return text.hashCode();
660 }
661
662 public static SearchSetting readFromString(String s) {
663 if (s.isEmpty())
664 return null;
665
666 SearchSetting result = new SearchSetting();
667
668 int index = 1;
669
670 result.mode = SearchMode.fromCode(s.charAt(0));
671 if (result.mode == null) {
672 result.mode = SearchMode.replace;
673 index = 0;
674 }
675
676 while (index < s.length()) {
677 if (s.charAt(index) == 'C') {
678 result.caseSensitive = true;
679 } else if (s.charAt(index) == 'R') {
680 result.regexSearch = true;
681 } else if (s.charAt(index) == 'A') {
682 result.allElements = true;
683 } else if (s.charAt(index) == 'M') {
684 result.mapCSSSearch = true;
685 } else if (s.charAt(index) == ' ') {
686 break;
687 } else {
688 Main.warn("Unknown char in SearchSettings: " + s);
689 break;
690 }
691 index++;
692 }
693
694 if (index < s.length() && s.charAt(index) == ' ') {
695 index++;
696 }
697
698 result.text = s.substring(index);
699
700 return result;
701 }
702
703 public String writeToString() {
704 if (text == null || text.isEmpty())
705 return "";
706
707 StringBuilder result = new StringBuilder();
708 result.append(mode.getCode());
709 if (caseSensitive) {
710 result.append('C');
711 }
712 if (regexSearch) {
713 result.append('R');
714 }
715 if (mapCSSSearch) {
716 result.append('M');
717 }
718 if (allElements) {
719 result.append('A');
720 }
721 result.append(' ')
722 .append(text);
723 return result.toString();
724 }
725 }
726
727 /**
728 * Refreshes the enabled state
729 *
730 */
731 @Override
732 protected void updateEnabledState() {
733 setEnabled(getEditLayer() != null);
734 }
735
736 @Override
737 public List<ActionParameter<?>> getActionParameters() {
738 return Collections.<ActionParameter<?>>singletonList(new SearchSettingsActionParameter(SEARCH_EXPRESSION));
739 }
740
741 public static String escapeStringForSearch(String s) {
742 return s.replace("\\", "\\\\").replace("\"", "\\\"");
743 }
744}
Note: See TracBrowser for help on using the repository browser.