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();