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