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

Last change on this file since 4948 was 4948, checked in by xeen, 12 years ago

fix #5117 – the selection buffer (middle mouse paste) on *nix systems shouldn’t be overwritten anymore by the search oder add/edit properties dialog. Please reopen the ticket, if there are any more occurences.

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