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

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

Fixed #4330 java.lang.ArrayIndexOutOfBoundsException: 6 >= 6, added SelectionEventManager (similar to DatasetEventManager)

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