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

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

applied #3961: patch by avar: Display help button in Search dialog

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