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

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

checkstyle: enable relevant whitespace checks and fix them

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