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

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

global use of Utils.joinAsHtmlUnorderedList()

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