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

Last change on this file since 4832 was 4832, checked in by simon04, 12 years ago

see #7230 - alternative search dialog for beta testing (enabled by setting dialog.search.new to true)

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