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

Last change on this file since 3175 was 3175, checked in by jttt, 14 years ago

Allow to set parameters for actions on toolbar. Used by SearchAction to set searching expression (see #4546)

  • Property svn:eol-style set to native
File size: 18.5 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.Font;
9import java.awt.GridBagLayout;
10import java.awt.event.ActionEvent;
11import java.awt.event.KeyEvent;
12import java.util.ArrayList;
13import java.util.Collection;
14import java.util.Collections;
15import java.util.HashSet;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Map;
19
20import javax.swing.ButtonGroup;
21import javax.swing.JCheckBox;
22import javax.swing.JLabel;
23import javax.swing.JOptionPane;
24import javax.swing.JPanel;
25import javax.swing.JRadioButton;
26
27import org.openstreetmap.josm.Main;
28import org.openstreetmap.josm.actions.ActionParameter;
29import org.openstreetmap.josm.actions.JosmAction;
30import org.openstreetmap.josm.actions.ParameterizedAction;
31import org.openstreetmap.josm.actions.ActionParameter.SearchSettingsActionParameter;
32import org.openstreetmap.josm.data.osm.DataSet;
33import org.openstreetmap.josm.data.osm.Filter;
34import org.openstreetmap.josm.data.osm.OsmPrimitive;
35import org.openstreetmap.josm.gui.ExtendedDialog;
36import org.openstreetmap.josm.gui.widgets.HistoryComboBox;
37import org.openstreetmap.josm.tools.GBC;
38import org.openstreetmap.josm.tools.Shortcut;
39
40public class SearchAction extends JosmAction implements ParameterizedAction {
41
42 public static final int DEFAULT_SEARCH_HISTORY_SIZE = 10;
43
44 private static final String SEARCH_EXPRESSION = "searchExpression";
45
46 public static enum SearchMode {
47 replace('R'), add('A'), remove('D'), in_selection('S');
48
49 private final char code;
50
51 SearchMode(char code) {
52 this.code = code;
53 }
54
55 public char getCode() {
56 return code;
57 }
58
59 public static SearchMode fromCode(char code) {
60 for (SearchMode mode: values()) {
61 if (mode.getCode() == code)
62 return mode;
63 }
64 return null;
65 }
66 }
67
68 public static final LinkedList<SearchSetting> searchHistory = new LinkedList<SearchSetting>();
69
70 private static SearchSetting lastSearch = null;
71
72 public SearchAction() {
73 super(tr("Search..."), "dialogs/search", tr("Search for objects."),
74 Shortcut.registerShortcut("system:find", tr("Search..."), KeyEvent.VK_F, Shortcut.GROUP_HOTKEY), true);
75 putValue("help", ht("/Action/Search"));
76 }
77
78 public void actionPerformed(ActionEvent e) {
79 if (!isEnabled())
80 return;
81 search();
82 }
83
84 public void actionPerformed(ActionEvent e, Map<String, Object> parameters) {
85 if (parameters.get(SEARCH_EXPRESSION) == null) {
86 actionPerformed(e);
87 } else {
88 searchWithoutHistory((SearchSetting) parameters.get(SEARCH_EXPRESSION));
89 }
90 }
91
92 public static List<String> getSearchExpressionHistory() {
93 ArrayList<String> ret = new ArrayList<String>(searchHistory.size());
94 for (SearchSetting ss: searchHistory) {
95 ret.add(ss.text);
96 }
97 return ret;
98 }
99
100 public static SearchSetting showSearchDialog(SearchSetting initialValues) {
101 if (initialValues == null) {
102 initialValues = new SearchSetting();
103 }
104 // -- prepare the combo box with the search expressions
105 //
106 JLabel label = new JLabel( initialValues instanceof Filter ? tr("Please enter a filter string.") : tr("Please enter a search string."));
107 final HistoryComboBox hcbSearchString = new HistoryComboBox();
108 hcbSearchString.setText(initialValues.text);
109 hcbSearchString.getEditor().selectAll();
110 hcbSearchString.getEditor().getEditorComponent().requestFocusInWindow();
111 hcbSearchString.setToolTipText(tr("Enter the search expression"));
112 // we have to reverse the history, because ComboBoxHistory will reverse it again
113 // in addElement()
114 //
115 List<String> searchExpressionHistory = getSearchExpressionHistory();
116 Collections.reverse(searchExpressionHistory);
117 hcbSearchString.setPossibleItems(searchExpressionHistory);
118
119 JRadioButton replace = new JRadioButton(tr("replace selection"), initialValues.mode == SearchMode.replace);
120 JRadioButton add = new JRadioButton(tr("add to selection"), initialValues.mode == SearchMode.add);
121 JRadioButton remove = new JRadioButton(tr("remove from selection"), initialValues.mode == SearchMode.remove);
122 JRadioButton in_selection = new JRadioButton(tr("find in selection"), initialValues.mode == SearchMode.in_selection);
123 ButtonGroup bg = new ButtonGroup();
124 bg.add(replace);
125 bg.add(add);
126 bg.add(remove);
127 bg.add(in_selection);
128
129 JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive);
130 JCheckBox regexSearch = new JCheckBox(tr("regular expression"), initialValues.regexSearch);
131
132 JPanel left = new JPanel(new GridBagLayout());
133 left.add(label, GBC.eop());
134 left.add(hcbSearchString, GBC.eop().fill(GBC.HORIZONTAL));
135 left.add(replace, GBC.eol());
136 left.add(add, GBC.eol());
137 left.add(remove, GBC.eol());
138 left.add(in_selection, GBC.eop());
139 left.add(caseSensitive, GBC.eol());
140 left.add(regexSearch, GBC.eol());
141
142 JPanel right = new JPanel();
143 JLabel description =
144 new JLabel("<html><ul>"
145 + "<li>"+tr("<b>Baker Street</b> - ''Baker'' and ''Street'' in any key or name.")+"</li>"
146 + "<li>"+tr("<b>\"Baker Street\"</b> - ''Baker Street'' in any key or name.")+"</li>"
147 + "<li>"+tr("<b>name:Bak</b> - ''Bak'' anywhere in the name.")+"</li>"
148 + "<li>"+tr("<b>type=route</b> - key ''type'' with value exactly ''route''.") + "</li>"
149 + "<li>"+tr("<b>type=*</b> - key ''type'' with any value. Try also <b>*=value</b>, <b>type=</b>, <b>*=*</b>, <b>*=</b>") + "</li>"
150 + "<li>"+tr("<b>-name:Bak</b> - not ''Bak'' in the name.")+"</li>"
151 + "<li>"+tr("<b>oneway?</b> - oneway=yes, true, 1 or on")+"</li>"
152 + "<li>"+tr("<b>foot:</b> - key=foot set to any value.")+"</li>"
153 + "<li>"+tr("<u>Special targets:</u>")+"</li>"
154 + "<li>"+tr("<b>type:</b> - type of the object (<b>node</b>, <b>way</b>, <b>relation</b>)")+"</li>"
155 + "<li>"+tr("<b>user:</b>... - all objects changed by user")+"</li>"
156 + "<li>"+tr("<b>user:anonymous</b> - all objects changed by anonymous users")+"</li>"
157 + "<li>"+tr("<b>id:</b>... - object with given ID (0 for new objects)")+"</li>"
158 + "<li>"+tr("<b>version:</b>... - object with given version (0 objects without an assigned version)")+"</li>"
159 + "<li>"+tr("<b>changeset:</b>... - object with given changeset id (0 objects without assigned changeset)")+"</li>"
160 + "<li>"+tr("<b>nodes:</b>... - object with given number of nodes (nodes:count or nodes:min-max)")+"</li>"
161 + "<li>"+tr("<b>tags:</b>... - object with given number of tags (tags:count or tags:min-max)")+"</li>"
162 + "<li>"+tr("<b>timestamp:</b>... - objects with this timestamp (<b>2009-11-12T14:51:09Z</b>, <b>2009-11-12</b> or <b>T14:51</b> ...)")+"</li>"
163 + "<li>"+tr("<b>modified</b> - all changed objects")+"</li>"
164 + "<li>"+tr("<b>selected</b> - all selected objects")+"</li>"
165 + "<li>"+tr("<b>incomplete</b> - all incomplete objects")+"</li>"
166 + "<li>"+tr("<b>untagged</b> - all untagged objects")+"</li>"
167 + "<li>"+tr("<b>child <i>expr</i></b> - all children of objects matching the expression")+"</li>"
168 + "<li>"+tr("<b>parent <i>expr</i></b> - all parents of objects matching the expression")+"</li>"
169 + "<li>"+tr("Use <b>|</b> or <b>OR</b> to combine with logical or")+"</li>"
170 + "<li>"+tr("Use <b>\"</b> to quote operators (e.g. if key contains <b>:</b>)") + "<br/>"
171 + 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>).")+"</li>"
172 + "<li>"+tr("Use <b>(</b> and <b>)</b> to group expressions")+"</li>"
173 + "</ul></html>");
174 description.setFont(description.getFont().deriveFont(Font.PLAIN));
175 right.add(description);
176
177 final JPanel p = new JPanel();
178 p.add(left);
179 p.add(right);
180 ExtendedDialog dialog = new ExtendedDialog(
181 Main.parent,
182 initialValues instanceof Filter ? tr("Filter") : tr("Search"),
183 new String[] {
184 initialValues instanceof Filter ? tr("Submit filter") : tr("Start Search"),
185 tr("Cancel")}
186 );
187 dialog.setButtonIcons(new String[] {"dialogs/search.png", "cancel.png"});
188 dialog.configureContextsensitiveHelp("/Action/Search", true /* show help button */);
189 dialog.setContent(p);
190 dialog.showDialog();
191 int result = dialog.getValue();
192
193 if(result != 1) return null;
194
195 // User pressed OK - let's perform the search
196 SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace
197 : (add.isSelected() ? SearchAction.SearchMode.add
198 : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection));
199 initialValues.text = hcbSearchString.getText();
200 initialValues.mode = mode;
201 initialValues.caseSensitive = caseSensitive.isSelected();
202 initialValues.regexSearch = regexSearch.isSelected();
203 return initialValues;
204 }
205
206 /**
207 * Launches the dialog for specifying search criteria and runs
208 * a search
209 */
210 public static void search() {
211 SearchSetting se = showSearchDialog(lastSearch);
212 if(se != null) {
213 searchWithHistory(se);
214 }
215 }
216
217 /**
218 * Adds the search specified by the settings in <code>s</code> to the
219 * search history and performs the search.
220 *
221 * @param s
222 */
223 public static void searchWithHistory(SearchSetting s) {
224 if(searchHistory.isEmpty() || !s.equals(searchHistory.getFirst())) {
225 searchHistory.addFirst(new SearchSetting(s));
226 }
227 int maxsize = Main.pref.getInteger("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE);
228 while (searchHistory.size() > maxsize) {
229 searchHistory.removeLast();
230 }
231 lastSearch = new SearchSetting(s);
232 search(s);
233 }
234
235 public static void searchWithoutHistory(SearchSetting s) {
236 lastSearch = new SearchSetting(s);
237 search(s);
238 }
239
240 public interface Function{
241 public Boolean isSomething(OsmPrimitive o);
242 }
243
244 public static int getSelection(SearchSetting s, Collection<OsmPrimitive> sel, Function f) {
245 int foundMatches = 0;
246 try {
247 String searchText = s.text;
248 if(s instanceof Filter){
249 searchText = "(" + s.text + ")" + (((Filter)s).applyForChildren ? ("| child (" + s.text + ")"): "");
250 searchText = (((Filter)s).inverted ? "-" : "") + "(" + searchText + ")";
251 }
252 /*System.out.println(searchText);*/
253 SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch);
254
255 if (s.mode == SearchMode.replace) {
256 sel.clear();
257 }
258
259 for (OsmPrimitive osm : Main.main.getCurrentDataSet().allNonDeletedCompletePrimitives()) {
260 if (s.mode == SearchMode.replace) {
261 if (matcher.match(osm)) {
262 sel.add(osm);
263 ++foundMatches;
264 }
265 } else if (s.mode == SearchMode.add && !f.isSomething(osm) && matcher.match(osm)) {
266 sel.add(osm);
267 ++foundMatches;
268 } else if (s.mode == SearchMode.remove && f.isSomething(osm) && matcher.match(osm)) {
269 sel.remove(osm);
270 ++foundMatches;
271 } else if (s.mode == SearchMode.in_selection && f.isSomething(osm)&& !matcher.match(osm)) {
272 sel.remove(osm);
273 ++foundMatches;
274 }
275 }
276 } catch (SearchCompiler.ParseError e) {
277 JOptionPane.showMessageDialog(
278 Main.parent,
279 e.getMessage(),
280 tr("Error"),
281 JOptionPane.ERROR_MESSAGE
282
283 );
284 }
285 return foundMatches;
286 }
287
288 public static void search(String search, SearchMode mode, boolean caseSensitive, boolean regexSearch) {
289 search(new SearchSetting(search, mode, caseSensitive, regexSearch));
290 }
291
292 public static void search(SearchSetting s) {
293 // FIXME: This is confusing. The GUI says nothing about loading primitives from an URL. We'd like to *search*
294 // for URLs in the current data set.
295 // Disabling until a better solution is in place
296 //
297 // if (search.startsWith("http://") || search.startsWith("ftp://") || search.startsWith("https://")
298 // || search.startsWith("file:/")) {
299 // SelectionWebsiteLoader loader = new SelectionWebsiteLoader(search, mode);
300 // if (loader.url != null && loader.url.getHost() != null) {
301 // Main.worker.execute(loader);
302 // return;
303 // }
304 // }
305
306 final DataSet ds = Main.main.getCurrentDataSet();
307 Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(ds.getSelected());
308 int foundMatches = getSelection(s, sel, new Function(){
309 public Boolean isSomething(OsmPrimitive o){
310 return ds.isSelected(o);
311 }
312 });
313 ds.setSelected(sel);
314 if (foundMatches == 0) {
315 String msg = null;
316 if (s.mode == SearchMode.replace) {
317 msg = tr("No match found for ''{0}''", s.text);
318 } else if (s.mode == SearchMode.add) {
319 msg = tr("Nothing added to selection by searching for ''{0}''", s.text);
320 } else if (s.mode == SearchMode.remove) {
321 msg = tr("Nothing removed from selection by searching for ''{0}''", s.text);
322 } else if (s.mode == SearchMode.in_selection) {
323 msg = tr("Nothing found in selection by searching for ''{0}''", s.text);
324 }
325 Main.map.statusLine.setHelpText(msg);
326 JOptionPane.showMessageDialog(
327 Main.parent,
328 msg,
329 tr("Warning"),
330 JOptionPane.WARNING_MESSAGE
331 );
332 } else {
333 Main.map.statusLine.setHelpText(tr("Found {0} matches", foundMatches));
334 }
335 }
336
337 public static class SearchSetting {
338 public String text;
339 public SearchMode mode;
340 public boolean caseSensitive;
341 public boolean regexSearch;
342
343 public SearchSetting() {
344 this("", SearchMode.replace, false /* case insensitive */, false /* no regexp */);
345 }
346
347 public SearchSetting(String text, SearchMode mode, boolean caseSensitive, boolean regexSearch) {
348 this.caseSensitive = caseSensitive;
349 this.regexSearch = regexSearch;
350 this.mode = mode;
351 this.text = text;
352 }
353
354 public SearchSetting(SearchSetting original) {
355 this(original.text, original.mode, original.caseSensitive, original.regexSearch);
356 }
357
358 @Override
359 public String toString() {
360 String cs = caseSensitive ?
361 /*case sensitive*/ trc("search", "CS") :
362 /*case insensitive*/ trc("search", "CI");
363 /*regex search*/
364 String rx = regexSearch ? (", " + trc("search", "RX")) : "";
365 return "\"" + text + "\" (" + cs + rx + ", " + mode + ")";
366 }
367
368 @Override
369 public boolean equals(Object other) {
370 if(!(other instanceof SearchSetting))
371 return false;
372 SearchSetting o = (SearchSetting) other;
373 return (o.caseSensitive == this.caseSensitive
374 && o.regexSearch == this.regexSearch
375 && o.mode.equals(this.mode)
376 && o.text.equals(this.text));
377 }
378
379 @Override
380 public int hashCode() {
381 return text.hashCode();
382 }
383
384 public static SearchSetting readFromString(String s) {
385 if (s.length() == 0)
386 return null;
387
388 SearchSetting result = new SearchSetting();
389
390 int index = 1;
391
392 result.mode = SearchMode.fromCode(s.charAt(0));
393 if (result.mode == null) {
394 result.mode = SearchMode.replace;
395 index = 0;
396 }
397
398 while (index < s.length()) {
399 if (s.charAt(index) == 'C') {
400 result.caseSensitive = true;
401 } else if (s.charAt(index) == 'R') {
402 result.regexSearch = true;
403 } else if (s.charAt(index) == ' ') {
404 break;
405 } else {
406 System.out.println("Uknown char in SearchSettings: " + s);
407 break;
408 }
409 index++;
410 }
411
412 if (index < s.length() && s.charAt(index) == ' ') {
413 index++;
414 }
415
416 result.text = s.substring(index);
417
418 return result;
419 }
420
421 public String writeToString() {
422 if (text == null || text.length() == 0)
423 return "";
424
425 StringBuilder result = new StringBuilder();
426 result.append(mode.getCode());
427 if (caseSensitive) {
428 result.append('C');
429 }
430 if (regexSearch) {
431 result.append('R');
432 }
433 result.append(' ');
434 result.append(text);
435 return result.toString();
436 }
437 }
438
439 /**
440 * Refreshes the enabled state
441 *
442 */
443 @Override
444 protected void updateEnabledState() {
445 setEnabled(getEditLayer() != null);
446 }
447
448 public List<ActionParameter<?>> getActionParameters() {
449 return Collections.<ActionParameter<?>>singletonList(new SearchSettingsActionParameter(SEARCH_EXPRESSION));
450 }
451}
Note: See TracBrowser for help on using the repository browser.