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

Last change on this file since 3102 was 3102, checked in by Gubaer, 14 years ago

fixed #4651: Ability to download incomplete relation from selection
fixed #4098: Popup Menu entry "download relation members" in relation dialog should be "download incomplete relation members"
fixed two NPEs in RelationListDialog and SelectionListDialog
refactored SelectionListDialog to support better user feedback (enabled/disabled buttons and menu items)
Finally removed the sort() method on DataSet, marked as FIXME since a long time.

CAVEAT: DataSet.getSelected() now returns an unmodifiable list instead of a copy of the selection list. This may lead to UnsupportedOperationExceptions in the next few days. I tried to make sure the JOSM core uses getSelected() only for reading, but I didn't check the plugins.

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