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

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