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

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

Accessibility - global use of JLabel.setLabelFor() + various fixes (javadoc, code style)

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