001 // License: GPL. Copyright 2007 by Immanuel Scholz and others 002 package org.openstreetmap.josm.actions.search; 003 004 import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005 import static org.openstreetmap.josm.tools.I18n.tr; 006 import static org.openstreetmap.josm.tools.I18n.trc; 007 008 import java.awt.Cursor; 009 import java.awt.Dimension; 010 import java.awt.FlowLayout; 011 import java.awt.Font; 012 import java.awt.GridBagLayout; 013 import java.awt.event.ActionEvent; 014 import java.awt.event.KeyEvent; 015 import java.awt.event.MouseAdapter; 016 import java.awt.event.MouseEvent; 017 import java.util.ArrayList; 018 import java.util.Arrays; 019 import java.util.Collection; 020 import java.util.Collections; 021 import java.util.HashSet; 022 import java.util.LinkedList; 023 import java.util.List; 024 import java.util.Map; 025 026 import javax.swing.ButtonGroup; 027 import javax.swing.JCheckBox; 028 import javax.swing.JLabel; 029 import javax.swing.JOptionPane; 030 import javax.swing.JPanel; 031 import javax.swing.JRadioButton; 032 import javax.swing.JTextField; 033 import javax.swing.text.BadLocationException; 034 035 import org.openstreetmap.josm.Main; 036 import org.openstreetmap.josm.actions.ActionParameter; 037 import org.openstreetmap.josm.actions.JosmAction; 038 import org.openstreetmap.josm.actions.ParameterizedAction; 039 import org.openstreetmap.josm.actions.ActionParameter.SearchSettingsActionParameter; 040 import org.openstreetmap.josm.actions.search.SearchCompiler.ParseError; 041 import org.openstreetmap.josm.data.osm.DataSet; 042 import org.openstreetmap.josm.data.osm.Filter; 043 import org.openstreetmap.josm.data.osm.OsmPrimitive; 044 import org.openstreetmap.josm.gui.ExtendedDialog; 045 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences; 046 import org.openstreetmap.josm.gui.preferences.ToolbarPreferences.ActionParser; 047 import org.openstreetmap.josm.gui.widgets.HistoryComboBox; 048 import org.openstreetmap.josm.tools.GBC; 049 import org.openstreetmap.josm.tools.Predicate; 050 import org.openstreetmap.josm.tools.Property; 051 import org.openstreetmap.josm.tools.Shortcut; 052 import org.openstreetmap.josm.tools.Utils; 053 054 public class SearchAction extends JosmAction implements ParameterizedAction { 055 056 public static final int DEFAULT_SEARCH_HISTORY_SIZE = 15; 057 058 private static final String SEARCH_EXPRESSION = "searchExpression"; 059 060 public static enum SearchMode { 061 replace('R'), add('A'), remove('D'), in_selection('S'); 062 063 private final char code; 064 065 SearchMode(char code) { 066 this.code = code; 067 } 068 069 public char getCode() { 070 return code; 071 } 072 073 public static SearchMode fromCode(char code) { 074 for (SearchMode mode: values()) { 075 if (mode.getCode() == code) 076 return mode; 077 } 078 return null; 079 } 080 } 081 082 private static LinkedList<SearchSetting> searchHistory = null; 083 084 public static Collection<SearchSetting> getSearchHistory() { 085 if (searchHistory == null) { 086 searchHistory = new LinkedList<SearchSetting>(); 087 for (String s: Main.pref.getCollection("search.history", Collections.<String>emptyList())) { 088 SearchSetting ss = SearchSetting.readFromString(s); 089 if (ss != null) { 090 searchHistory.add(ss); 091 } 092 } 093 } 094 095 return searchHistory; 096 } 097 098 public static void saveToHistory(SearchSetting s) { 099 if(searchHistory.isEmpty() || !s.equals(searchHistory.getFirst())) { 100 searchHistory.addFirst(new SearchSetting(s)); 101 } 102 int maxsize = Main.pref.getInteger("search.history-size", DEFAULT_SEARCH_HISTORY_SIZE); 103 while (searchHistory.size() > maxsize) { 104 searchHistory.removeLast(); 105 } 106 List<String> savedHistory = new ArrayList<String>(); 107 for (SearchSetting item: searchHistory) { 108 savedHistory.add(item.writeToString()); 109 } 110 Main.pref.putCollection("search.history", savedHistory); 111 } 112 113 public static List<String> getSearchExpressionHistory() { 114 ArrayList<String> ret = new ArrayList<String>(getSearchHistory().size()); 115 for (SearchSetting ss: getSearchHistory()) { 116 ret.add(ss.text); 117 } 118 return ret; 119 } 120 121 private static SearchSetting lastSearch = null; 122 123 public SearchAction() { 124 super(tr("Search..."), "dialogs/search", tr("Search for objects."), 125 Shortcut.registerShortcut("system:find", tr("Search..."), KeyEvent.VK_F, Shortcut.CTRL), true); 126 putValue("help", ht("/Action/Search")); 127 } 128 129 public void actionPerformed(ActionEvent e) { 130 if (!isEnabled()) 131 return; 132 search(); 133 } 134 135 public void actionPerformed(ActionEvent e, Map<String, Object> parameters) { 136 if (parameters.get(SEARCH_EXPRESSION) == null) { 137 actionPerformed(e); 138 } else { 139 searchWithoutHistory((SearchSetting) parameters.get(SEARCH_EXPRESSION)); 140 } 141 } 142 143 private static class DescriptionTextBuilder { 144 145 StringBuilder s = new StringBuilder(4096); 146 147 public StringBuilder append(String string) { 148 return s.append(string); 149 } 150 151 StringBuilder appendItem(String item) { 152 return append("<li>").append(item).append("</li>\n"); 153 } 154 155 StringBuilder appendItemHeader(String itemHeader) { 156 return append("<li class=\"header\">").append(itemHeader).append("</li>\n"); 157 } 158 159 @Override 160 public String toString() { 161 return s.toString(); 162 } 163 } 164 165 private static class SearchKeywordRow extends JPanel { 166 167 private final HistoryComboBox hcb; 168 169 public SearchKeywordRow(HistoryComboBox hcb) { 170 super(new FlowLayout(FlowLayout.LEFT)); 171 this.hcb = hcb; 172 } 173 174 public SearchKeywordRow addTitle(String title) { 175 add(new JLabel(tr("{0}: ", title))); 176 return this; 177 } 178 179 public SearchKeywordRow addKeyword(String displayText, final String insertText, String description, String... examples) { 180 JLabel label = new JLabel("<html>" 181 + "<style>td{border:1px solid gray; font-weight:normal;}</style>" 182 + "<table><tr><td>" + displayText + "</td></tr></table></html>"); 183 add(label); 184 if (description != null || examples.length > 0) { 185 label.setToolTipText("<html>" 186 + description 187 + (examples.length > 0 ? ("<ul><li>" + Utils.join("</li><li>", Arrays.asList(examples)) + "</li></ul>") : "") 188 + "</html>"); 189 } 190 if (insertText != null) { 191 label.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); 192 label.addMouseListener(new MouseAdapter() { 193 194 @Override 195 public void mouseClicked(MouseEvent e) { 196 try { 197 JTextField tf = (JTextField) hcb.getEditor().getEditorComponent(); 198 tf.getDocument().insertString(tf.getCaretPosition(), " " + insertText, null); 199 } catch (BadLocationException ex) { 200 throw new RuntimeException(ex.getMessage(), ex); 201 } 202 } 203 }); 204 } 205 return this; 206 } 207 } 208 209 public static SearchSetting showSearchDialog(SearchSetting initialValues) { 210 if (initialValues == null) { 211 initialValues = new SearchSetting(); 212 } 213 // -- prepare the combo box with the search expressions 214 // 215 JLabel label = new JLabel( initialValues instanceof Filter ? tr("Filter string:") : tr("Search string:")); 216 final HistoryComboBox hcbSearchString = new HistoryComboBox(); 217 hcbSearchString.setText(initialValues.text); 218 hcbSearchString.setToolTipText(tr("Enter the search expression")); 219 // we have to reverse the history, because ComboBoxHistory will reverse it again 220 // in addElement() 221 // 222 List<String> searchExpressionHistory = getSearchExpressionHistory(); 223 Collections.reverse(searchExpressionHistory); 224 hcbSearchString.setPossibleItems(searchExpressionHistory); 225 hcbSearchString.setPreferredSize(new Dimension(40, hcbSearchString.getPreferredSize().height)); 226 227 JRadioButton replace = new JRadioButton(tr("replace selection"), initialValues.mode == SearchMode.replace); 228 JRadioButton add = new JRadioButton(tr("add to selection"), initialValues.mode == SearchMode.add); 229 JRadioButton remove = new JRadioButton(tr("remove from selection"), initialValues.mode == SearchMode.remove); 230 JRadioButton in_selection = new JRadioButton(tr("find in selection"), initialValues.mode == SearchMode.in_selection); 231 ButtonGroup bg = new ButtonGroup(); 232 bg.add(replace); 233 bg.add(add); 234 bg.add(remove); 235 bg.add(in_selection); 236 237 final JCheckBox caseSensitive = new JCheckBox(tr("case sensitive"), initialValues.caseSensitive); 238 JCheckBox allElements = new JCheckBox(tr("all objects"), initialValues.allElements); 239 allElements.setToolTipText(tr("Also include incomplete and deleted objects in search.")); 240 final JCheckBox regexSearch = new JCheckBox(tr("regular expression"), initialValues.regexSearch); 241 final JCheckBox addOnToolbar = new JCheckBox(tr("add toolbar button"), false); 242 243 JPanel top = new JPanel(new GridBagLayout()); 244 top.add(label, GBC.std().insets(0, 0, 5, 0)); 245 top.add(hcbSearchString, GBC.eol().fill(GBC.HORIZONTAL)); 246 JPanel left = new JPanel(new GridBagLayout()); 247 left.add(replace, GBC.eol()); 248 left.add(add, GBC.eol()); 249 left.add(remove, GBC.eol()); 250 left.add(in_selection, GBC.eop()); 251 left.add(caseSensitive, GBC.eol()); 252 if(Main.pref.getBoolean("expert", false)) 253 { 254 left.add(allElements, GBC.eol()); 255 left.add(regexSearch, GBC.eol()); 256 left.add(addOnToolbar, GBC.eol()); 257 } 258 259 final JPanel right; 260 if (Main.pref.getBoolean("dialog.search.new", true)) { 261 right = new JPanel(new GridBagLayout()); 262 buildHintsNew(right, hcbSearchString); 263 } else { 264 right = new JPanel(); 265 buildHints(right); 266 } 267 268 final JPanel p = new JPanel(new GridBagLayout()); 269 p.add(top, GBC.eol().fill(GBC.HORIZONTAL).insets(5, 5, 5, 0)); 270 p.add(left, GBC.std().anchor(GBC.NORTH).insets(5, 10, 10, 0)); 271 p.add(right, GBC.eol()); 272 ExtendedDialog dialog = new ExtendedDialog( 273 Main.parent, 274 initialValues instanceof Filter ? tr("Filter") : tr("Search"), 275 new String[] { 276 initialValues instanceof Filter ? tr("Submit filter") : tr("Start Search"), 277 tr("Cancel")} 278 ) { 279 @Override 280 protected void buttonAction(int buttonIndex, ActionEvent evt) { 281 if (buttonIndex == 0) { 282 try { 283 SearchCompiler.compile(hcbSearchString.getText(), caseSensitive.isSelected(), regexSearch.isSelected()); 284 super.buttonAction(buttonIndex, evt); 285 } catch (ParseError e) { 286 JOptionPane.showMessageDialog( 287 Main.parent, 288 tr("Search expression is not valid: \n\n {0}", e.getMessage()), 289 tr("Invalid search expression"), 290 JOptionPane.ERROR_MESSAGE); 291 } 292 } else { 293 super.buttonAction(buttonIndex, evt); 294 } 295 } 296 }; 297 dialog.setButtonIcons(new String[] {"dialogs/search.png", "cancel.png"}); 298 dialog.configureContextsensitiveHelp("/Action/Search", true /* show help button */); 299 dialog.setContent(p); 300 dialog.showDialog(); 301 int result = dialog.getValue(); 302 303 if(result != 1) return null; 304 305 // User pressed OK - let's perform the search 306 SearchMode mode = replace.isSelected() ? SearchAction.SearchMode.replace 307 : (add.isSelected() ? SearchAction.SearchMode.add 308 : (remove.isSelected() ? SearchAction.SearchMode.remove : SearchAction.SearchMode.in_selection)); 309 initialValues.text = hcbSearchString.getText(); 310 initialValues.mode = mode; 311 initialValues.caseSensitive = caseSensitive.isSelected(); 312 initialValues.allElements = allElements.isSelected(); 313 initialValues.regexSearch = regexSearch.isSelected(); 314 315 if (addOnToolbar.isSelected()) { 316 ToolbarPreferences.ActionDefinition aDef = 317 new ToolbarPreferences.ActionDefinition(Main.main.menu.search); 318 aDef.getParameters().put("searchExpression", initialValues); 319 // parametrized action definition is now composed 320 ActionParser actionParser = new ToolbarPreferences.ActionParser(null); 321 String res = actionParser.saveAction(aDef); 322 323 Collection<String> t = new LinkedList<String>(ToolbarPreferences.getToolString()); 324 // add custom search button to toolbar preferences 325 if (!t.contains(res)) t.add(res); 326 Main.pref.putCollection("toolbar", t); 327 Main.toolbar.refreshToolbarControl(); 328 } 329 return initialValues; 330 } 331 332 private static void buildHints(JPanel right) { 333 DescriptionTextBuilder descriptionText = new DescriptionTextBuilder(); 334 descriptionText.append("<html><style>li.header{font-size:110%; list-style-type:none; margin-top:5px;}</style><ul>"); 335 descriptionText.appendItem(tr("<b>Baker Street</b> - ''Baker'' and ''Street'' in any key")); 336 descriptionText.appendItem(tr("<b>\"Baker Street\"</b> - ''Baker Street'' in any key")); 337 descriptionText.appendItem(tr("<b>key:Bak</b> - ''Bak'' anywhere in the key ''key''")); 338 descriptionText.appendItem(tr("<b>-key:Bak</b> - ''Bak'' nowhere in the key ''key''")); 339 descriptionText.appendItem(tr("<b>key=value</b> - key ''key'' with value exactly ''value''")); 340 descriptionText.appendItem(tr("<b>key=*</b> - key ''key'' with any value. Try also <b>*=value</b>, <b>key=</b>, <b>*=*</b>, <b>*=</b>")); 341 descriptionText.appendItem(tr("<b>key:</b> - key ''key'' set to any value")); 342 descriptionText.appendItem(tr("<b>key?</b> - key ''key'' with the value ''yes'', ''true'', ''1'' or ''on''")); 343 if(Main.pref.getBoolean("expert", false)) 344 { 345 descriptionText.appendItemHeader(tr("Special targets")); 346 /* 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>)")); 347 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>user:</b>... - objects changed by user")); 348 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>user:anonymous</b> - objects changed by anonymous users")); 349 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>id:</b>... - objects with given ID (0 for new objects)")); 350 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>version:</b>... - objects with given version (0 objects without an assigned version)")); 351 /* 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)")); 352 /* 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)")); 353 /* 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)")); 354 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>role:</b>... - objects with given role in a relation")); 355 /* 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 ...)")); 356 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>timestamp:</b>min/max - objects with last modification within range")); 357 /* 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)")); 358 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>modified</b> - all changed objects")); 359 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>selected</b> - all selected objects")); 360 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>incomplete</b> - all incomplete objects")); 361 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>untagged</b> - all untagged objects")); 362 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>closed</b> - all closed ways (a node is not considered closed)")); 363 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>child <i>expr</i></b> - all children of objects matching the expression")); 364 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("<b>parent <i>expr</i></b> - all parents of objects matching the expression")); 365 /* 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")); 366 /* 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")); 367 } 368 /* I18n: don't translate the bold text keyword */ descriptionText.appendItem(tr("Use <b>|</b> or <b>OR</b> to combine with logical or")); 369 descriptionText.appendItem(tr("Use <b>\"</b> to quote operators (e.g. if key contains <b>:</b>)") 370 + "<br/>" 371 + 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>).")); 372 descriptionText.appendItem(tr("Use <b>(</b> and <b>)</b> to group expressions")); 373 descriptionText.append("</ul></html>"); 374 JLabel description = new JLabel(descriptionText.toString()); 375 description.setFont(description.getFont().deriveFont(Font.PLAIN)); 376 right.add(description); 377 } 378 379 private static void buildHintsNew(JPanel right, HistoryComboBox hcbSearchString) { 380 right.add(new SearchKeywordRow(hcbSearchString) 381 .addTitle(tr("basic examples")) 382 .addKeyword(tr("Baker Street"), null, tr("''Baker'' and ''Street'' in any key")) 383 .addKeyword(tr("\"Baker Street\""), "\"\"", tr("''Baker Street'' in any key")) 384 , GBC.eol()); 385 right.add(new SearchKeywordRow(hcbSearchString) 386 .addTitle(tr("basics")) 387 .addKeyword("<i>key</i>:<i>valuefragment</i>", null, tr("''valuefragment'' anywhere in ''key''"), "name:str matches name=Bakerstreet") 388 .addKeyword("-<i>key</i>:<i>valuefragment</i>", null, tr("''valuefragment'' nowhere in ''key''")) 389 .addKeyword("<i>key</i>=<i>value</i>", null, tr("''key'' with exactly ''value''")) 390 .addKeyword("<i>key</i>=*", null, tr("''key'' with any value")) 391 .addKeyword("*=<i>value</i>", null, tr("''value'' in any key")) 392 .addKeyword("<i>key</i>=", null, tr("matches if ''key'' exists")) 393 , GBC.eol()); 394 right.add(new SearchKeywordRow(hcbSearchString) 395 .addTitle(tr("combinators")) 396 .addKeyword("<i>expr</i> <i>expr</i>", null, tr("logical and (both expressions have to be satisfied)")) 397 .addKeyword("<i>expr</i> | <i>expr</i>", "| ", tr("logical or (at least one expression has to be satisfied)")) 398 .addKeyword("<i>expr</i> OR <i>expr</i>", "OR ", tr("logical or (at least one expression has to be satisfied)")) 399 .addKeyword("-<i>expr</i>", null, tr("logical not")) 400 .addKeyword("(<i>expr</i>)", "()", tr("use parenthesis to group expressions")) 401 .addKeyword("\"key\"=\"value\"", "\"\"=\"\"", 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\"") 402 , GBC.eol()); 403 404 if (Main.pref.getBoolean("expert", false)) { 405 right.add(new SearchKeywordRow(hcbSearchString) 406 .addTitle(tr("objects")) 407 .addKeyword("type:node", "type:node ", tr("all ways")) 408 .addKeyword("type:way", "type:way ", tr("all ways")) 409 .addKeyword("type:relation", "type:relation ", tr("all relations")) 410 .addKeyword("closed", "closed ", tr("all closed ways")) 411 , GBC.eol()); 412 right.add(new SearchKeywordRow(hcbSearchString) 413 .addTitle(tr("metadata")) 414 .addKeyword("user:", "user:", tr("objects changed by user", "user:anonymous")) 415 .addKeyword("id:", "id:", tr("objects with given ID"), "id:0 (new objects)") 416 .addKeyword("version:", "version:", tr("objects with given version"), "version:0 (objects without an assigned version)") 417 .addKeyword("changeset:", "changeset:", tr("objects with given changeset ID"), "changeset:0 (objects without an assigned changeset)") 418 .addKeyword("timestamp:", "timestamp:", tr("objects with last modification timestamp within range"), "timestamp:2012/", "timestamp:2008/2011-02-04T12") 419 , GBC.eol()); 420 right.add(new SearchKeywordRow(hcbSearchString) 421 .addTitle(tr("properties")) 422 .addKeyword("nodes:<i>20-</i>", "nodes:", tr("objects with at least 20 nodes")) 423 .addKeyword("tags:<i>5-10</i>", "tags:", tr("objects having 5 to 10 tags")) 424 .addKeyword("role:", "role:", tr("objects with given role in a relation")) 425 .addKeyword("areasize:<i>-100</i>", "areasize:", tr("closed ways with an area of 100 m\u00b2")) 426 , GBC.eol()); 427 right.add(new SearchKeywordRow(hcbSearchString) 428 .addTitle(tr("state")) 429 .addKeyword("modified", "modified ", tr("all modified objects")) 430 .addKeyword("new", "new ", tr("all new objects")) 431 .addKeyword("selected", "selected ", tr("all selected objects")) 432 .addKeyword("incomplete", "incomplete ", tr("all incomplete objects")) 433 , GBC.eol()); 434 right.add(new SearchKeywordRow(hcbSearchString) 435 .addTitle(tr("relations")) 436 .addKeyword("child <i>expr</i>", "child ", tr("all children of objects matching the expression"), "child building") 437 .addKeyword("parent <i>expr</i>", "parent ", tr("all parents of objects matching the expression"), "parent bus_stop") 438 , GBC.eol()); 439 right.add(new SearchKeywordRow(hcbSearchString) 440 .addTitle(tr("view")) 441 .addKeyword("inview", "inview ", tr("objects in current view")) 442 .addKeyword("allinview", "allinview ", tr("objects (and all its way nodes / relation members) in current view")) 443 .addKeyword("indownloadedarea", "indownloadedarea ", tr("objects in downloaded area")) 444 .addKeyword("allindownloadedarea", "allindownloadedarea ", tr("objects (and all its way nodes / relation members) in downloaded area")) 445 , GBC.eol()); 446 } 447 } 448 449 /** 450 * Launches the dialog for specifying search criteria and runs 451 * a search 452 */ 453 public static void search() { 454 SearchSetting se = showSearchDialog(lastSearch); 455 if(se != null) { 456 searchWithHistory(se); 457 } 458 } 459 460 /** 461 * Adds the search specified by the settings in <code>s</code> to the 462 * search history and performs the search. 463 * 464 * @param s 465 */ 466 public static void searchWithHistory(SearchSetting s) { 467 saveToHistory(s); 468 lastSearch = new SearchSetting(s); 469 search(s); 470 } 471 472 public static void searchWithoutHistory(SearchSetting s) { 473 lastSearch = new SearchSetting(s); 474 search(s); 475 } 476 477 public static int getSelection(SearchSetting s, Collection<OsmPrimitive> sel, Predicate<OsmPrimitive> p) { 478 int foundMatches = 0; 479 try { 480 String searchText = s.text; 481 SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch); 482 483 if (s.mode == SearchMode.replace) { 484 sel.clear(); 485 } 486 487 Collection<OsmPrimitive> all; 488 if(s.allElements) { 489 all = Main.main.getCurrentDataSet().allPrimitives(); 490 } else { 491 all = Main.main.getCurrentDataSet().allNonDeletedCompletePrimitives(); 492 } 493 for (OsmPrimitive osm : all) { 494 if (s.mode == SearchMode.replace) { 495 if (matcher.match(osm)) { 496 sel.add(osm); 497 ++foundMatches; 498 } 499 } else if (s.mode == SearchMode.add && !p.evaluate(osm) && matcher.match(osm)) { 500 sel.add(osm); 501 ++foundMatches; 502 } else if (s.mode == SearchMode.remove && p.evaluate(osm) && matcher.match(osm)) { 503 sel.remove(osm); 504 ++foundMatches; 505 } else if (s.mode == SearchMode.in_selection && p.evaluate(osm) && !matcher.match(osm)) { 506 sel.remove(osm); 507 ++foundMatches; 508 } 509 } 510 } catch (SearchCompiler.ParseError e) { 511 JOptionPane.showMessageDialog( 512 Main.parent, 513 e.getMessage(), 514 tr("Error"), 515 JOptionPane.ERROR_MESSAGE 516 517 ); 518 } 519 return foundMatches; 520 } 521 522 /** 523 * Version of getSelection that is customized for filter, but should 524 * also work in other context. 525 * 526 * @param s the search settings 527 * @param all the collection of all the primitives that should be considered 528 * @param p the property that should be set/unset if something is found 529 */ 530 public static void getSelection(SearchSetting s, Collection<OsmPrimitive> all, Property<OsmPrimitive, Boolean> p) { 531 try { 532 String searchText = s.text; 533 if (s instanceof Filter && ((Filter)s).inverted) { 534 searchText = String.format("-(%s)", searchText); 535 } 536 SearchCompiler.Match matcher = SearchCompiler.compile(searchText, s.caseSensitive, s.regexSearch); 537 538 for (OsmPrimitive osm : all) { 539 if (s.mode == SearchMode.replace) { 540 if (matcher.match(osm)) { 541 p.set(osm, true); 542 } else { 543 p.set(osm, false); 544 } 545 } else if (s.mode == SearchMode.add && !p.get(osm) && matcher.match(osm)) { 546 p.set(osm, true); 547 } else if (s.mode == SearchMode.remove && p.get(osm) && matcher.match(osm)) { 548 p.set(osm, false); 549 } else if (s.mode == SearchMode.in_selection && p.get(osm) && !matcher.match(osm)) { 550 p.set(osm, false); 551 } 552 } 553 } catch (SearchCompiler.ParseError e) { 554 JOptionPane.showMessageDialog( 555 Main.parent, 556 e.getMessage(), 557 tr("Error"), 558 JOptionPane.ERROR_MESSAGE 559 560 ); 561 } 562 } 563 564 public static void search(String search, SearchMode mode) { 565 search(new SearchSetting(search, mode, false, false, false)); 566 } 567 568 public static void search(SearchSetting s) { 569 570 final DataSet ds = Main.main.getCurrentDataSet(); 571 Collection<OsmPrimitive> sel = new HashSet<OsmPrimitive>(ds.getAllSelected()); 572 int foundMatches = getSelection(s, sel, new Predicate<OsmPrimitive>(){ 573 @Override 574 public boolean evaluate(OsmPrimitive o){ 575 return ds.isSelected(o); 576 } 577 }); 578 ds.setSelected(sel); 579 if (foundMatches == 0) { 580 String msg = null; 581 if (s.mode == SearchMode.replace) { 582 msg = tr("No match found for ''{0}''", s.text); 583 } else if (s.mode == SearchMode.add) { 584 msg = tr("Nothing added to selection by searching for ''{0}''", s.text); 585 } else if (s.mode == SearchMode.remove) { 586 msg = tr("Nothing removed from selection by searching for ''{0}''", s.text); 587 } else if (s.mode == SearchMode.in_selection) { 588 msg = tr("Nothing found in selection by searching for ''{0}''", s.text); 589 } 590 Main.map.statusLine.setHelpText(msg); 591 JOptionPane.showMessageDialog( 592 Main.parent, 593 msg, 594 tr("Warning"), 595 JOptionPane.WARNING_MESSAGE 596 ); 597 } else { 598 Main.map.statusLine.setHelpText(tr("Found {0} matches", foundMatches)); 599 } 600 } 601 602 public static class SearchSetting { 603 public String text; 604 public SearchMode mode; 605 public boolean caseSensitive; 606 public boolean regexSearch; 607 public boolean allElements; 608 609 public SearchSetting() { 610 this("", SearchMode.replace, false /* case insensitive */, 611 false /* no regexp */, false /* only useful primitives */); 612 } 613 614 public SearchSetting(String text, SearchMode mode, boolean caseSensitive, 615 boolean regexSearch, boolean allElements) { 616 this.caseSensitive = caseSensitive; 617 this.regexSearch = regexSearch; 618 this.allElements = allElements; 619 this.mode = mode; 620 this.text = text; 621 } 622 623 public SearchSetting(SearchSetting original) { 624 this(original.text, original.mode, original.caseSensitive, 625 original.regexSearch, original.allElements); 626 } 627 628 @Override 629 public String toString() { 630 String cs = caseSensitive ? 631 /*case sensitive*/ trc("search", "CS") : 632 /*case insensitive*/ trc("search", "CI"); 633 String rx = regexSearch ? (", " + 634 /*regex search*/ trc("search", "RX")) : ""; 635 String all = allElements ? (", " + 636 /*all elements*/ trc("search", "A")) : ""; 637 return "\"" + text + "\" (" + cs + rx + all + ", " + mode + ")"; 638 } 639 640 @Override 641 public boolean equals(Object other) { 642 if(!(other instanceof SearchSetting)) 643 return false; 644 SearchSetting o = (SearchSetting) other; 645 return (o.caseSensitive == this.caseSensitive 646 && o.regexSearch == this.regexSearch 647 && o.allElements == this.allElements 648 && o.mode.equals(this.mode) 649 && o.text.equals(this.text)); 650 } 651 652 @Override 653 public int hashCode() { 654 return text.hashCode(); 655 } 656 657 public static SearchSetting readFromString(String s) { 658 if (s.length() == 0) 659 return null; 660 661 SearchSetting result = new SearchSetting(); 662 663 int index = 1; 664 665 result.mode = SearchMode.fromCode(s.charAt(0)); 666 if (result.mode == null) { 667 result.mode = SearchMode.replace; 668 index = 0; 669 } 670 671 while (index < s.length()) { 672 if (s.charAt(index) == 'C') { 673 result.caseSensitive = true; 674 } else if (s.charAt(index) == 'R') { 675 result.regexSearch = true; 676 } else if (s.charAt(index) == 'A') { 677 result.allElements = true; 678 } else if (s.charAt(index) == ' ') { 679 break; 680 } else { 681 System.out.println("Unknown char in SearchSettings: " + s); 682 break; 683 } 684 index++; 685 } 686 687 if (index < s.length() && s.charAt(index) == ' ') { 688 index++; 689 } 690 691 result.text = s.substring(index); 692 693 return result; 694 } 695 696 public String writeToString() { 697 if (text == null || text.length() == 0) 698 return ""; 699 700 StringBuilder result = new StringBuilder(); 701 result.append(mode.getCode()); 702 if (caseSensitive) { 703 result.append('C'); 704 } 705 if (regexSearch) { 706 result.append('R'); 707 } 708 if (allElements) { 709 result.append('A'); 710 } 711 result.append(' '); 712 result.append(text); 713 return result.toString(); 714 } 715 } 716 717 /** 718 * Refreshes the enabled state 719 * 720 */ 721 @Override 722 protected void updateEnabledState() { 723 setEnabled(getEditLayer() != null); 724 } 725 726 public List<ActionParameter<?>> getActionParameters() { 727 return Collections.<ActionParameter<?>>singletonList(new SearchSettingsActionParameter(SEARCH_EXPRESSION)); 728 } 729 730 public static String escapeStringForSearch(String s) { 731 return s.replace("\\", "\\\\").replace("\"", "\\\""); 732 } 733 }