001 /* 002 * The contents of this file are subject to the Mozilla Public 003 * License Version 1.1 (the "License"); you may not use this file 004 * except in compliance with the License. You may obtain a copy of 005 * the License at http://www.mozilla.org/MPL/ 006 * 007 * Software distributed under the License is distributed on an "AS 008 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 009 * implied. See the License for the specific language governing 010 * rights and limitations under the License. 011 * 012 * The Original Code is Knowtator. 013 * 014 * The Initial Developer of the Original Code is University of Colorado. 015 * Copyright (C) 2005-2008. All Rights Reserved. 016 * 017 * Knowtator was developed by the Center for Computational Pharmacology 018 * (http://compbio.uchcs.edu) at the University of Colorado Health 019 * Sciences Center School of Medicine with support from the National 020 * Library of Medicine. 021 * 022 * Current information about Knowtator can be obtained at 023 * http://knowtator.sourceforge.net/ 024 * 025 * Contributor(s): 026 * Philip V. Ogren <philip@ogren.info> (Original Author) 027 */ 028 package edu.uchsc.ccp.knowtator.wizards; 029 030 import java.awt.BorderLayout; 031 import java.awt.GridBagConstraints; 032 import java.awt.GridBagLayout; 033 import java.awt.Insets; 034 import java.awt.Toolkit; 035 import java.awt.event.ActionEvent; 036 import java.awt.event.ActionListener; 037 import java.util.List; 038 import java.util.regex.Matcher; 039 import java.util.regex.Pattern; 040 import java.util.regex.PatternSyntaxException; 041 042 import javax.swing.JButton; 043 import javax.swing.JCheckBox; 044 import javax.swing.JDialog; 045 import javax.swing.JFrame; 046 import javax.swing.JLabel; 047 import javax.swing.JOptionPane; 048 import javax.swing.JPanel; 049 import javax.swing.JTextField; 050 import javax.swing.event.CaretEvent; 051 import javax.swing.event.CaretListener; 052 053 import edu.stanford.smi.protege.model.Cls; 054 import edu.stanford.smi.protege.model.Instance; 055 import edu.stanford.smi.protege.model.Project; 056 import edu.stanford.smi.protege.ui.DisplayUtilities; 057 import edu.stanford.smi.protege.util.CollectionUtilities; 058 import edu.stanford.smi.protege.util.ComponentFactory; 059 import edu.stanford.smi.protege.util.ComponentUtilities; 060 import edu.stanford.smi.protege.util.SelectableList; 061 import edu.uchsc.ccp.knowtator.KnowtatorManager; 062 import edu.uchsc.ccp.knowtator.KnowtatorProjectUtil; 063 import edu.uchsc.ccp.knowtator.Span; 064 import edu.uchsc.ccp.knowtator.event.EventHandler; 065 import edu.uchsc.ccp.knowtator.event.SelectedSpanChangeEvent; 066 import edu.uchsc.ccp.knowtator.event.SelectedSpanChangeListener; 067 import edu.uchsc.ccp.knowtator.textsource.TextSourceAccessException; 068 import edu.uchsc.ccp.knowtator.textsource.TextSourceChangeEvent; 069 import edu.uchsc.ccp.knowtator.textsource.TextSourceChangeListener; 070 import edu.uchsc.ccp.knowtator.ui.KnowtatorTextPane; 071 072 public class FindAnnotateDialog extends JDialog implements ActionListener, TextSourceChangeListener, CaretListener, 073 SelectedSpanChangeListener 074 075 { 076 static final long serialVersionUID = 0; 077 078 JLabel searchForLabel; 079 080 // TODO this should probably be a drop down 081 JTextField searchStringTextField; 082 083 JLabel annotateWithLabel; 084 085 SelectableList annotationClsList; 086 087 JButton selectClsButton; 088 089 JCheckBox regularExpressionCheckBox; 090 091 JCheckBox matchCaseCheckBox; 092 093 JCheckBox matchWordsCheckBox; 094 095 JCheckBox spanCapturingGroupsCheckBox; 096 097 JButton findNextButton; 098 099 JButton findPreviousButton; 100 101 JButton annotateButton; 102 103 JButton annotateAllButton; 104 105 JButton undoButton; 106 107 JButton closeButton; 108 109 Cls annotationCls; 110 111 Project project; 112 113 JFrame parent; 114 115 KnowtatorManager manager; 116 117 KnowtatorProjectUtil kpu; 118 119 String text; 120 121 int searchMark = 0; 122 123 KnowtatorTextPane textPane; 124 125 Matcher matcher; 126 127 boolean currentlySelectedSpanAlreadyAnnotated = false; 128 129 public FindAnnotateDialog(Project project, JFrame parent, KnowtatorTextPane textPane, KnowtatorManager manager) { 130 super(parent, "Find/Annotate", false); 131 this.project = project; 132 this.parent = parent; 133 this.textPane = textPane; 134 this.manager = manager; 135 initGUI(); 136 EventHandler.getInstance().addSelectedSpanChangeListener(this); 137 } 138 139 protected void initGUI() { 140 setSize(630, 180); 141 setLocation(WizardUtil.getCentralDialogLocation(parent, this)); 142 143 searchForLabel = new JLabel("Search for"); 144 searchForLabel.setDisplayedMnemonic('s'); 145 146 searchStringTextField = new JTextField(); 147 searchStringTextField.addCaretListener(createCaretListener()); 148 searchForLabel.setLabelFor(searchStringTextField); 149 150 annotateWithLabel = new JLabel("Annotate with"); 151 annotateWithLabel.setDisplayedMnemonic('o'); 152 annotateWithLabel.setLabelFor(selectClsButton); 153 154 annotationClsList = ComponentFactory.createSingleItemList(null); 155 annotationClsList.setCellRenderer(manager.getRenderer()); 156 157 selectClsButton = new JButton("Choose class"); 158 selectClsButton.addActionListener(this); 159 selectClsButton.setMnemonic('o'); 160 161 regularExpressionCheckBox = new JCheckBox("Regular expression search"); 162 regularExpressionCheckBox.setMnemonic('r'); 163 regularExpressionCheckBox.addActionListener(this); 164 matchCaseCheckBox = new JCheckBox("Match case"); 165 matchCaseCheckBox.setMnemonic('m'); 166 matchCaseCheckBox.addActionListener(this); 167 matchWordsCheckBox = new JCheckBox("Whole words only"); 168 matchWordsCheckBox.setMnemonic('w'); 169 matchWordsCheckBox.addActionListener(this); 170 spanCapturingGroupsCheckBox = new JCheckBox("Annotate capturing groups"); 171 spanCapturingGroupsCheckBox.setMnemonic('g'); 172 spanCapturingGroupsCheckBox.setEnabled(false); 173 174 findNextButton = new JButton("Find next"); 175 findNextButton.setMnemonic('f'); 176 findNextButton.addActionListener(this); 177 findNextButton.setEnabled(false); 178 findPreviousButton = new JButton("Find previous"); 179 findPreviousButton.setMnemonic('p'); 180 findPreviousButton.addActionListener(this); 181 findPreviousButton.setEnabled(false); 182 annotateButton = new JButton("Annotate"); 183 annotateButton.setMnemonic('a'); 184 annotateButton.addActionListener(this); 185 annotateButton.setEnabled(false); 186 annotateAllButton = new JButton("Annotate all"); 187 annotateAllButton.setMnemonic('l'); 188 annotateAllButton.addActionListener(this); 189 annotateAllButton.setEnabled(false); 190 undoButton = new JButton("Undo"); 191 undoButton.setMnemonic('u'); 192 undoButton.setEnabled(false); 193 undoButton.addActionListener(this); 194 closeButton = new JButton("Close"); 195 closeButton.setMnemonic('c'); 196 closeButton.addActionListener(this); 197 198 JPanel searchPanel = new JPanel(new GridBagLayout()); 199 searchPanel.add(searchForLabel, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.WEST, 200 GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 4, 4)); 201 searchPanel.add(searchStringTextField, new GridBagConstraints(1, 0, 2, 1, 1.0, 1.0, GridBagConstraints.CENTER, 202 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 4, 4)); 203 searchPanel.add(annotateWithLabel, new GridBagConstraints(0, 1, 1, 1, 0, 0, GridBagConstraints.WEST, 204 GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 4, 4)); 205 searchPanel.add(annotationClsList, new GridBagConstraints(1, 1, 1, 1, 1.0, 0, GridBagConstraints.CENTER, 206 GridBagConstraints.HORIZONTAL, new Insets(0, 0, 0, 0), 0, 0)); 207 searchPanel.add(selectClsButton, new GridBagConstraints(2, 1, 1, 1, 0, 0, GridBagConstraints.CENTER, 208 GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 4, 4)); 209 210 JPanel buttonsPanel = new JPanel(new GridBagLayout()); 211 buttonsPanel.add(findNextButton, new GridBagConstraints(0, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, 212 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); 213 buttonsPanel.add(findPreviousButton, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.CENTER, 214 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); 215 buttonsPanel.add(annotateButton, new GridBagConstraints(0, 1, 1, 1, 0, 0, GridBagConstraints.CENTER, 216 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); 217 buttonsPanel.add(annotateAllButton, new GridBagConstraints(1, 1, 1, 1, 0, 0, GridBagConstraints.CENTER, 218 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); 219 buttonsPanel.add(undoButton, new GridBagConstraints(0, 2, 1, 1, 0, 0, GridBagConstraints.CENTER, 220 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); 221 buttonsPanel.add(closeButton, new GridBagConstraints(1, 2, 1, 1, 0, 0, GridBagConstraints.CENTER, 222 GridBagConstraints.HORIZONTAL, new Insets(2, 2, 2, 2), 0, 0)); 223 224 JPanel checkBoxesPanel = new JPanel(new GridBagLayout()); 225 checkBoxesPanel.add(regularExpressionCheckBox, new GridBagConstraints(0, 0, 1, 1, 0, 0, 226 GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); 227 checkBoxesPanel.add(matchCaseCheckBox, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.WEST, 228 GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); 229 checkBoxesPanel.add(matchWordsCheckBox, new GridBagConstraints(2, 0, 1, 1, 0, 0, GridBagConstraints.WEST, 230 GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); 231 checkBoxesPanel.add(spanCapturingGroupsCheckBox, new GridBagConstraints(3, 0, 1, 1, 0, 0, 232 GridBagConstraints.WEST, GridBagConstraints.NONE, new Insets(2, 2, 2, 2), 0, 0)); 233 234 JPanel dialogPanel = new JPanel(new GridBagLayout()); 235 dialogPanel.add(searchPanel, new GridBagConstraints(0, 0, 1, 1, 1.0, 1.0, GridBagConstraints.WEST, 236 GridBagConstraints.HORIZONTAL, new Insets(10, 10, 0, 20), 0, 0)); 237 dialogPanel.add(buttonsPanel, new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.EAST, 238 GridBagConstraints.NONE, new Insets(10, 0, 0, 10), 0, 0)); 239 dialogPanel.add(checkBoxesPanel, new GridBagConstraints(0, 2, 2, 1, 0, 0, GridBagConstraints.CENTER, 240 GridBagConstraints.NONE, new Insets(0, 0, 10, 0), 0, 0)); 241 242 setLayout(new BorderLayout()); 243 add(dialogPanel, BorderLayout.CENTER); 244 245 } 246 247 public void setVisible(boolean visible) { 248 enableButtons(); 249 super.setVisible(visible); 250 } 251 252 private void enableButtons() { 253 String searchString = searchStringTextField.getText(); 254 255 if (!searchString.equals("")) { 256 findNextButton.setEnabled(true); 257 findPreviousButton.setEnabled(true); 258 // annotateAllButton.setEnabled(true); 259 } else { 260 findNextButton.setEnabled(false); 261 findPreviousButton.setEnabled(false); 262 // annotateAllButton.setEnabled(false); 263 } 264 List<Span> selectedSpans = manager.getSelectedSpans(); 265 if (selectedSpans.size() > 0) { 266 annotateButton.setEnabled(true); 267 } else { 268 annotateButton.setEnabled(false); 269 } 270 if (regularExpressionCheckBox.isSelected()) { 271 spanCapturingGroupsCheckBox.setEnabled(true); 272 } else 273 spanCapturingGroupsCheckBox.setEnabled(false); 274 } 275 276 private CaretListener createCaretListener() { 277 return new CaretListener() { 278 public void caretUpdate(CaretEvent caretEvent) { 279 enableButtons(); 280 } 281 }; 282 } 283 284 private Pattern preparePattern() throws PatternSyntaxException { 285 boolean regex = regularExpressionCheckBox.isSelected(); 286 boolean matchCase = matchCaseCheckBox.isSelected(); 287 boolean matchWord = matchWordsCheckBox.isSelected(); 288 String userEntry = searchStringTextField.getText(); 289 290 int mask = 0; 291 String prefix = ""; 292 String base = ""; 293 String suffix = ""; 294 295 if (!matchCase) { 296 mask = Pattern.CASE_INSENSITIVE; 297 } 298 if (matchWord) { 299 prefix = "(?:^|\\W+)("; 300 suffix = ")(?:\\W+|$)"; 301 } else { 302 prefix = "("; 303 suffix = ")"; 304 } 305 if (regex) { 306 base = userEntry; 307 } else { 308 base = Pattern.quote(userEntry); 309 } 310 311 return Pattern.compile(prefix + base + suffix, mask); 312 } 313 314 private void findNext() { 315 reset();// we only need to reset if the search string has changed. 316 find(true); 317 } 318 319 private void findPrevious() { 320 reset();// we only need to reset if the search string has changed. 321 find(false); 322 } 323 324 private void reset() { 325 try { 326 Pattern pattern = preparePattern(); 327 matcher = pattern.matcher(text); 328 } catch (PatternSyntaxException pse) { 329 pse.printStackTrace(); 330 JOptionPane.showMessageDialog(this, "Search string is not a valid regular expression."); 331 } 332 } 333 334 private void found(Matcher matcher) { 335 currentlySelectedSpanAlreadyAnnotated = false; 336 textPane.clearSelectionHighlights(); 337 textPane.hideHighlights(); 338 if (!spanCapturingGroupsCheckBox.isSelected() || matcher.groupCount() == 1) { 339 if (matcher.group(1) != null) { 340 int start = matcher.start(1); 341 int end = matcher.end(1); 342 if (textPane.select(new Span(start, end), true)) { 343 currentlySelectedSpanAlreadyAnnotated = true; 344 Toolkit.getDefaultToolkit().beep(); 345 } 346 searchMark = end; 347 } 348 } else { 349 boolean spanHighlighted = false; 350 for (int i = 2; i <= matcher.groupCount(); i++) { 351 int start = matcher.start(i); 352 int end = matcher.end(i); 353 354 if (start <= end && start >= 0) { 355 spanHighlighted = true; 356 if (textPane.select(new Span(start, end), true)) { 357 currentlySelectedSpanAlreadyAnnotated = true; 358 Toolkit.getDefaultToolkit().beep(); 359 } 360 searchMark = end; 361 } else { 362 searchMark = matcher.end(); 363 } 364 } 365 if (!spanHighlighted) { 366 JOptionPane.showMessageDialog(this, 367 "The search string was found but none of the capturing groups were matched.\n" 368 + "Please consider revising your search string or\n" 369 + "unchecking 'Annotate capturing groups'.", "capturing groups not matched", 370 JOptionPane.WARNING_MESSAGE); 371 } 372 } 373 textPane.showSelectionHighlights(); 374 textPane.showAnnotationHighlights(); 375 } 376 377 private void find(boolean forward) { 378 if (forward) { 379 if (matcher.find(searchMark)) { 380 found(matcher); 381 } else if (searchMark != 0) { 382 searchMark = 0; 383 Toolkit.getDefaultToolkit().beep(); 384 find(true); 385 } else { 386 JOptionPane.showMessageDialog(this, "Search string was not found"); 387 } 388 } else { 389 int previousStart = -1; 390 matcher.reset(); 391 while (matcher.find()) { 392 int start = matcher.start(); 393 int end = matcher.end(); 394 if (end < searchMark) { 395 previousStart = start; 396 } else { 397 if (previousStart > -1 && matcher.find(previousStart)) { 398 found(matcher); 399 searchMark = previousStart + 1; // add plus one - 400 // otherwise it will 401 // take two 'next finds' 402 // to get to the next 403 // find. 404 enableButtons(); 405 return; 406 } 407 } 408 } 409 410 matcher.reset(); 411 while (matcher.find()) { 412 previousStart = matcher.start(); 413 } 414 if (matcher.find(previousStart)) { 415 found(matcher); 416 searchMark = previousStart + 1; 417 Toolkit.getDefaultToolkit().beep(); 418 } 419 420 } 421 enableButtons(); 422 } 423 424 private void annotate() { 425 if (annotationClsList.getSelection().size() == 0) { 426 selectCls(); 427 } 428 if (currentlySelectedSpanAlreadyAnnotated) { 429 int result = JOptionPane.showConfirmDialog(this, 430 "The currently selected text has already been annotated.\n" + "Press OK to create an annotation", 431 "duplicate annotation warning", JOptionPane.OK_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE); 432 if (result == JOptionPane.CANCEL_OPTION) 433 return; 434 } 435 436 Cls cls = (Cls) annotationClsList.getSelectedValue(); 437 List<Span> selectedSpans = manager.getSelectedSpans(); 438 if (selectedSpans.size() > 0) { 439 manager.createAnnotation(cls); 440 } 441 searchMark++; 442 find(true); 443 } 444 445 private void selectCls() { 446 List<Cls> rootClses = manager.getRootClses(); 447 Instance instance = DisplayUtilities.pickCls(this, project.getKnowledgeBase(), rootClses); 448 if (instance != null) { 449 annotationCls = (Cls) instance; 450 ComponentUtilities.setListValues(annotationClsList, CollectionUtilities.createCollection(instance)); 451 annotationClsList.setSelectedIndex(0); 452 enableButtons(); 453 } 454 } 455 456 private void close() { 457 searchMark = 0; 458 textPane.showAllHighlights(); 459 setVisible(false); 460 } 461 462 public void actionPerformed(ActionEvent actionEvent) { 463 Object source = actionEvent.getSource(); 464 if (source == annotateButton) { 465 annotate(); 466 } 467 if (source == findNextButton) { 468 findNext(); 469 } 470 if (source == findPreviousButton) { 471 findPrevious(); 472 } 473 if (source == selectClsButton) { 474 selectCls(); 475 } 476 if (source == closeButton) { 477 close(); 478 } 479 if (source == regularExpressionCheckBox || source == matchCaseCheckBox || source == matchWordsCheckBox) 480 reset(); 481 if (source == regularExpressionCheckBox) { 482 enableButtons(); 483 } 484 } 485 486 public void textSourceChanged(TextSourceChangeEvent event) { 487 try { 488 if (event.getTextSource() != null) 489 text = event.getTextSource().getText(); 490 } catch (TextSourceAccessException tsae) { 491 close(); 492 } 493 } 494 495 public void caretUpdate(CaretEvent caretEvent) { 496 searchMark = caretEvent.getMark(); 497 } 498 499 public void spanSelectionChanged(SelectedSpanChangeEvent ssce) { 500 currentlySelectedSpanAlreadyAnnotated = false; 501 } 502 } 503 504 // } 505 // catch(PatternSyntaxException pse) 506 // { 507 // throw new PatternSyntaxException( 508 // "The string you entered is not a valid regular expression.\n" 509 // +pse.getDescription(), 510 // userEntry, pse.getIndex()); 511 // } 512 513 // 514 // String searchString = searchStringTextField.getText(); 515 // int matchIndex = text.indexOf(searchString, searchMark); 516 // if(matchIndex != -1) 517 // { 518 // int start = matchIndex; 519 // int end = matchIndex+searchString.length(); 520 // textPane.clearSelectionHighlights(); 521 // textPane.hideHighlights(); 522 // textPane.select(new Span(start,end)); 523 // textPane.showAnnotationHighlights(); 524 // searchMark = end; 525 // } 526 // else if (searchMark != 0) 527 // { 528 // searchMark = 0; 529 // Toolkit.getDefaultToolkit().beep(); 530 // find();