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

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

Rework console output:

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