SearchAndAnnotatePanel.java
001 /*
002  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
003  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
004  *
005  *  This file is part of GATE (see http://gate.ac.uk/), and is free
006  *  software, licenced under the GNU Library General Public License,
007  *  Version 2, June 1991 (in the distribution as file licence.html,
008  *  and also available at http://gate.ac.uk/gate/licence.html).
009  *
010  *  Thomas Heitz, Nov 21, 2007
011  *
012  *  $Id: SchemaAnnotationEditor.java 9221 2007-11-14 17:46:37Z valyt $
013  */
014 
015 package gate.gui.annedit;
016 
017 import java.awt.*;
018 import java.awt.event.*;
019 import java.util.*;
020 import java.util.regex.*;
021 
022 import javax.swing.*;
023 import javax.swing.event.*;
024 import javax.swing.event.DocumentEvent;
025 import javax.swing.event.DocumentListener;
026 
027 import gate.*;
028 import gate.event.*;
029 import gate.gui.MainFrame;
030 import gate.util.*;
031 
032 /**
033  * Build a GUI for searching and annotating annotations in a text.
034  * It needs to be called from a {@link gate.gui.annedit.OwnedAnnotationEditor}.
035  
036  <p>Here is an example how to add it to a JPanel panel.
037  *
038  <pre>
039  * SearchAndAnnotatePanel searchPanel =
040  *           new SearchAndAnnotatePanel(panel.getBackground(), this, window);
041  * panel.add(searchPanel);
042  </pre>
043  */
044 public class SearchAndAnnotatePanel extends JPanel {
045 
046   private static final long serialVersionUID = 1L;
047 
048   /**
049    * The annotation editor that use this search and annotate panel.
050    */
051   private OwnedAnnotationEditor annotationEditor;
052 
053   /**
054    * Window that contains the annotation editor.
055    */
056   private Window annotationEditorWindow;
057 
058   /**
059    * Listener for updating the list of searched annotations.
060    */
061   protected AnnotationSetListener annotationSetListener;
062   
063   /**
064    * The box used to host the search pane.
065    */
066   protected Box searchBox;
067 
068   /**
069    * The pane containing the UI for search and annotate functionality.
070    */
071   protected JPanel searchPane;
072   
073   /**
074    * Text field for searching
075    */
076   protected JTextField searchTextField;
077   
078   /**
079    * Checkbox for enabling RegEx searching.
080    */
081   protected JCheckBox searchRegExpChk;
082   
083   /**
084    * Help button that gives predefined regular expressions.
085    */
086   protected JButton helpRegExpButton;
087 
088   /**
089    * Checkbox for enabling case sensitive searching.
090    */
091   protected JCheckBox searchCaseSensChk;
092   
093   /**
094    * Checkbox for enabling whole word searching.
095    */
096   protected JCheckBox searchWholeWordsChk;
097 
098   /**
099    * Checkbox for enabling whole word searching.
100    */
101   protected JCheckBox searchHighlightsChk;
102 
103   /**
104    * Checkbox for showing the search UI.
105    */
106   protected JCheckBox searchEnabledCheck;
107 
108   /**
109    * Shared instance of the matcher.
110    */
111   protected Matcher matcher;
112 
113   protected FindFirstAction findFirstAction;
114   
115   protected FindPreviousAction findPreviousAction;
116 
117   protected FindNextAction findNextAction;
118   
119   protected AnnotateMatchAction annotateMatchAction;
120   
121   protected AnnotateAllMatchesAction annotateAllMatchesAction;
122   
123   protected UndoAnnotateAllMatchesAction undoAnnotateAllMatchesAction;
124 
125   protected int nextMatchStartsFrom;
126   
127   protected String content;
128 
129   /**
130    * Start and end index of the all the matches. 
131    */
132   protected LinkedList<Vector<Integer>> matchedIndexes;
133   
134   /**
135    * List of annotations ID that have been created
136    * by the AnnotateAllMatchesAction. 
137    */
138   protected LinkedList<Annotation> annotateAllAnnotationsID;
139   
140   protected SmallButton firstSmallButton;
141 
142   protected SmallButton annotateAllMatchesSmallButton;
143   
144 
145 
146   public SearchAndAnnotatePanel(Color color,
147           OwnedAnnotationEditor annotationEditor, Window window) {
148 
149     this.annotationEditor = annotationEditor;
150     annotationEditorWindow = window;
151 
152     initGui(color);
153 
154     // initially searchBox is collapsed
155     searchBox.remove(searchPane);
156     searchCaseSensChk.setVisible(false);
157     searchRegExpChk.setVisible(false);
158     searchWholeWordsChk.setVisible(false);
159     searchHighlightsChk.setVisible(false);
160 
161     // if the user never gives the focus to the textPane then
162     // there will never be any selection in it so we force it
163     getOwner().getTextComponent().requestFocusInWindow();
164 
165     initListeners();
166     
167     content = getOwner().getDocument().getContent().toString();
168   }
169 
170   /**
171    * Build the GUI with JPanels and Boxes.
172    *
173    @param color Color of the background.
174    * _                    _        _          _         _
175    * V Search & Annotate |_| Case |_| Regexp |_| Whole |_| Highlights
176    *  _______________________________________________
177    * |V_Searched_Expression__________________________| |?|
178    
179    * |First| |Prev.| |Next| |Annotate| |Ann. all next|
180    */
181   protected void initGui(Color color) {
182 
183     JPanel mainPane = new JPanel();
184     mainPane.setLayout(new BoxLayout(mainPane, BoxLayout.Y_AXIS));
185     mainPane.setBackground(color);
186     mainPane.setBorder(BorderFactory.createEmptyBorder(5353));
187 
188     setLayout(new BorderLayout());
189     add(mainPane, BorderLayout.CENTER);
190 
191     searchBox = Box.createVerticalBox();
192     String aTitle = "Open Search & Annotate tool";
193     JLabel label = new JLabel(aTitle);
194     searchBox.setMinimumSize(
195       new Dimension(label.getPreferredSize().width, 0));    
196     searchBox.setAlignmentX(Component.LEFT_ALIGNMENT);
197 
198     JPanel firstLinePane = new JPanel();
199     firstLinePane.setAlignmentX(Component.LEFT_ALIGNMENT);
200     firstLinePane.setAlignmentY(Component.TOP_ALIGNMENT);
201     firstLinePane.setLayout(new BoxLayout(firstLinePane, BoxLayout.Y_AXIS));
202     firstLinePane.setBackground(color);
203     Box hBox = Box.createHorizontalBox();
204       searchEnabledCheck = new JCheckBox(aTitle, MainFrame.getIcon("closed")false);
205       searchEnabledCheck.setSelectedIcon(MainFrame.getIcon("expanded"));
206       searchEnabledCheck.setBackground(color);
207       searchEnabledCheck.setToolTipText("<html>Allows to search for an "
208         +"expression and<br>annotate one or all the matches.</html>");
209     hBox.add(searchEnabledCheck);
210     hBox.add(Box.createHorizontalStrut(5));
211       searchCaseSensChk = new JCheckBox("Case"true);
212       searchCaseSensChk.setToolTipText("Case sensitive search.");
213       searchCaseSensChk.setBackground(color);
214     hBox.add(searchCaseSensChk);
215     hBox.add(Box.createHorizontalStrut(5));
216       searchRegExpChk = new JCheckBox("Regexp"false);
217       searchRegExpChk.setToolTipText("Regular expression search.");
218       searchRegExpChk.setBackground(color);
219     hBox.add(searchRegExpChk);
220     hBox.add(Box.createHorizontalStrut(5));
221       searchWholeWordsChk = new JCheckBox("Whole"false);
222       searchWholeWordsChk.setBackground(color);
223       searchWholeWordsChk.setToolTipText("Whole word search.");
224     hBox.add(searchWholeWordsChk);
225     hBox.add(Box.createHorizontalStrut(5));
226       searchHighlightsChk = new JCheckBox("Highlights"false);
227       searchHighlightsChk.setToolTipText(
228         "Restrict the search on the highlighted annotations.");
229       searchHighlightsChk.setBackground(color);
230     hBox.add(searchHighlightsChk);
231     hBox.add(Box.createHorizontalGlue());
232     firstLinePane.add(hBox);
233     searchBox.add(firstLinePane);
234 
235     searchPane = new JPanel();
236     searchPane.setAlignmentX(Component.LEFT_ALIGNMENT);
237     searchPane.setAlignmentY(Component.TOP_ALIGNMENT);
238     searchPane.setLayout(new BoxLayout(searchPane, BoxLayout.Y_AXIS));
239     searchPane.setBackground(color);
240       hBox = Box.createHorizontalBox();
241       hBox.setBorder(BorderFactory.createEmptyBorder(3050));
242       hBox.add(Box.createHorizontalStrut(5));
243         searchTextField = new JTextField(10);
244         searchTextField.setToolTipText("Enter an expression to search for.");
245         //disallow vertical expansion
246         searchTextField.setMaximumSize(new Dimension(Integer.MAX_VALUE, 
247             searchTextField.getPreferredSize().height));
248         searchTextField.addActionListener(new ActionListener() {
249           public void actionPerformed(ActionEvent arg0) {
250             findFirstAction.actionPerformed(null);
251           }
252         });
253       hBox.add(searchTextField);
254       hBox.add(Box.createHorizontalStrut(2));
255       helpRegExpButton = new JButton("?");
256       helpRegExpButton.setMargin(new Insets(0202));
257       helpRegExpButton.setToolTipText("GATE search expression builder.");
258       helpRegExpButton.addActionListener(new SearchExpressionsAction(
259         searchTextField, annotationEditorWindow, searchRegExpChk));
260       hBox.add(helpRegExpButton);
261       hBox.add(Box.createHorizontalGlue());
262     searchPane.add(hBox);
263       hBox = Box.createHorizontalBox();
264       hBox.add(Box.createHorizontalStrut(5));
265         findFirstAction = new FindFirstAction();
266         firstSmallButton = new SmallButton(findFirstAction);
267       hBox.add(firstSmallButton);
268       hBox.add(Box.createHorizontalStrut(5));
269         findPreviousAction = new FindPreviousAction();
270         findPreviousAction.setEnabled(false);
271       hBox.add(new SmallButton(findPreviousAction));
272       hBox.add(Box.createHorizontalStrut(5));
273         findNextAction = new FindNextAction();
274         findNextAction.setEnabled(false);
275       hBox.add(new SmallButton(findNextAction));
276       hBox.add(Box.createHorizontalStrut(5));
277         annotateMatchAction = new AnnotateMatchAction();
278         annotateMatchAction.setEnabled(false);
279       hBox.add(new SmallButton(annotateMatchAction));
280       hBox.add(Box.createHorizontalStrut(5));
281         annotateAllMatchesAction = new AnnotateAllMatchesAction();
282         undoAnnotateAllMatchesAction = new UndoAnnotateAllMatchesAction();
283         annotateAllMatchesSmallButton =
284           new SmallButton(annotateAllMatchesAction);
285         annotateAllMatchesAction.setEnabled(false);
286         undoAnnotateAllMatchesAction.setEnabled(false);
287       hBox.add(annotateAllMatchesSmallButton);
288       hBox.add(Box.createHorizontalStrut(5));
289     searchPane.add(hBox);
290     searchBox.add(searchPane);
291 
292     mainPane.add(searchBox);
293   }
294 
295   protected void initListeners() {
296 
297     searchEnabledCheck.addActionListener(new ActionListener() {
298       public void actionPerformed(ActionEvent e) {
299         if (searchEnabledCheck.isSelected()) {
300           //add the search box if not already there
301           if (!searchBox.isAncestorOf(searchPane)) {
302             // if empty, initialise the search field to the text
303             // of the current annotation
304             String searchText = searchTextField.getText()
305             if (searchText == null || searchText.trim().length() == 0) {
306               if (annotationEditor.getAnnotationCurrentlyEdited() != null
307                 && getOwner() != null) {
308                 String annText = getOwner().getDocument().getContent().
309                     toString().substring(
310                       annotationEditor.getAnnotationCurrentlyEdited()
311                         .getStartNode().getOffset().intValue(),
312                       annotationEditor.getAnnotationCurrentlyEdited()
313                         .getEndNode().getOffset().intValue());
314                 searchTextField.setText(annText);
315               }
316             }
317             searchBox.add(searchPane);
318           }
319           searchEnabledCheck.setText("");
320           searchCaseSensChk.setVisible(true);
321           searchRegExpChk.setVisible(true);
322           searchWholeWordsChk.setVisible(true);
323           searchHighlightsChk.setVisible(true);
324           searchTextField.requestFocusInWindow();
325           searchTextField.selectAll();
326           annotationEditorWindow.pack();
327           annotationEditor.setPinnedMode(true);
328 
329         else {
330           if(searchBox.isAncestorOf(searchPane)){
331             searchEnabledCheck.setText("Open Search & Annotate tool");
332             searchBox.remove(searchPane);
333             searchCaseSensChk.setVisible(false);
334             searchRegExpChk.setVisible(false);
335             searchWholeWordsChk.setVisible(false);
336             searchHighlightsChk.setVisible(false);
337             if (annotationEditor.getAnnotationCurrentlyEdited() != null) {
338               annotationEditor.setEditingEnabled(true);
339             }
340             annotationEditorWindow.pack();
341           }
342         }
343       }
344     });
345 
346     this.addAncestorListener(new AncestorListener() {
347       public void ancestorAdded(AncestorEvent event) {
348         // put the selection of the document into the search text field
349         if (searchTextField.getText().trim().length() == 0
350           && getOwner().getTextComponent().getSelectedText() != null) {
351           searchTextField.setText(getOwner().getTextComponent().getSelectedText());
352         }
353       }
354       public void ancestorRemoved(AncestorEvent event) {
355         // if the editor window is closed
356         enableActions(false);
357       }
358       public void ancestorMoved(AncestorEvent event) {
359         // do nothing
360       }
361     });
362 
363     searchTextField.getDocument().addDocumentListener(new DocumentListener(){
364       public void changedUpdate(DocumentEvent e) {
365         enableActions(false);
366       }
367       public void insertUpdate(DocumentEvent e) {
368         enableActions(false);
369       }
370       public void removeUpdate(DocumentEvent e) {
371         enableActions(false);
372       }
373     });
374 
375     searchCaseSensChk.addActionListener(new ActionListener() {
376       public void actionPerformed(ActionEvent e) {
377         enableActions(false);
378       }
379     });
380 
381     searchRegExpChk.addActionListener(new ActionListener() {
382       public void actionPerformed(ActionEvent e) {
383         enableActions(false);
384       }
385     });
386 
387     searchWholeWordsChk.addActionListener(new ActionListener() {
388       public void actionPerformed(ActionEvent e) {
389         enableActions(false);
390       }
391     });
392 
393       searchHighlightsChk.addActionListener(new ActionListener() {
394       public void actionPerformed(ActionEvent e) {
395         enableActions(false);
396       }
397     });
398 
399   }
400 
401   private void enableActions(boolean state){
402     findPreviousAction.setEnabled(state);
403     findNextAction.setEnabled(state);
404     annotateMatchAction.setEnabled(state);
405     annotateAllMatchesAction.setEnabled(state);
406     if (annotateAllMatchesSmallButton.getAction()
407             .equals(undoAnnotateAllMatchesAction)) {
408       annotateAllMatchesSmallButton.setAction(annotateAllMatchesAction);
409     }
410   }
411 
412   private boolean isAnnotationEditorReady() {
413     if (!annotationEditor.editingFinished()
414      || getOwner() == null
415      || annotationEditor.getAnnotationCurrentlyEdited() == null
416      || annotationEditor.getAnnotationSetCurrentlyEdited() == null) {
417       annotationEditorWindow.setVisible(false);
418       JOptionPane.showMessageDialog(annotationEditorWindow,
419         (annotationEditor.getAnnotationCurrentlyEdited() == null?
420           "Please select an existing annotation\n"
421           "or create a new one then select it."
422           :
423           "Please set all required features in the feature table."),
424         "GATE",
425         JOptionPane.INFORMATION_MESSAGE);
426       annotationEditorWindow.setVisible(true);
427       return false;
428     else {
429       return true
430     }
431   }
432 
433   protected class FindFirstAction extends AbstractAction{
434     private static final long serialVersionUID = 1L;
435 
436     public FindFirstAction(){
437       super("First");
438       super.putValue(SHORT_DESCRIPTION, "Finds the first occurrence.");
439       super.putValue(MNEMONIC_KEY, KeyEvent.VK_F);
440     }
441 
442     public void actionPerformed(ActionEvent evt){
443       if (!isAnnotationEditorReady()) { return}
444       annotationEditor.setPinnedMode(true);
445       annotationEditor.setEditingEnabled(false);
446       String patternText = searchTextField.getText();
447       Pattern pattern;
448 
449       try {
450         String prefixPattern = searchWholeWordsChk.isSelected() "\\b":"";
451         prefixPattern += searchRegExpChk.isSelected() "":"\\Q";
452         String suffixPattern = searchRegExpChk.isSelected() "":"\\E";
453         suffixPattern += searchWholeWordsChk.isSelected() "\\b":"";
454         patternText = prefixPattern + patternText + suffixPattern;
455         // TODO: Pattern.UNICODE_CASE prevent insensitive case to work
456         // for Java 1.5 but works with Java 1.6
457         pattern = searchCaseSensChk.isSelected() ?
458                   Pattern.compile(patternText:
459                   Pattern.compile(patternText, Pattern.CASE_INSENSITIVE);
460 
461       catch(PatternSyntaxException e) {
462         // hides the annotator window
463         // to be able to see the dialog window
464         annotationEditorWindow.setVisible(false);
465         JOptionPane.showMessageDialog(annotationEditorWindow,
466           "Invalid regular expression.\n\n"
467           + e.toString().replaceFirst("^.+PatternSyntaxException: """),
468           "GATE",
469           JOptionPane.INFORMATION_MESSAGE);
470         annotationEditorWindow.setVisible(true);
471         return;
472       }
473 
474       matcher = pattern.matcher(content);
475       boolean found = false;
476       int start = -1;
477       int end = -1;
478       nextMatchStartsFrom = 0;
479       while (matcher.find(nextMatchStartsFrom&& !found) {
480         start = (matcher.groupCount()>0)?matcher.start(1):matcher.start();
481         end = (matcher.groupCount()>0)?matcher.end(1):matcher.end();
482         found = false;
483         if (searchHighlightsChk.isSelected()) {
484           javax.swing.text.Highlighter.Highlight[] highlights =
485             getOwner().getTextComponent().getHighlighter().getHighlights();
486           for (javax.swing.text.Highlighter.Highlight h : highlights) {
487             if (h.getStartOffset() <= start && h.getEndOffset() >= end) {
488               found = true;
489               break;
490             }
491           }
492         else {
493           found = true;
494         }
495         nextMatchStartsFrom = end;
496       }
497 
498       if (found) {
499         findNextAction.setEnabled(true);
500         annotateMatchAction.setEnabled(true);
501         annotateAllMatchesAction.setEnabled(false);
502         matchedIndexes = new LinkedList<Vector<Integer>>();
503         Vector<Integer> v = new Vector<Integer>(2);
504         v.add(start);
505         v.add(end);
506         matchedIndexes.add(v);
507         getOwner().getTextComponent().select(start, end);
508         annotationEditor.placeDialog(start, end);
509 
510       else {
511         // no match found
512         findNextAction.setEnabled(false);
513         annotateMatchAction.setEnabled(false);
514       }
515       findPreviousAction.setEnabled(false);
516     }
517   }
518   
519   protected class FindPreviousAction extends AbstractAction {
520     private static final long serialVersionUID = 1L;
521 
522     public FindPreviousAction() {
523       super("Prev.");
524       super.putValue(SHORT_DESCRIPTION, "Finds the previous occurrence.");
525       super.putValue(MNEMONIC_KEY, KeyEvent.VK_P);
526     }
527 
528     public void actionPerformed(ActionEvent evt) {
529       if (!isAnnotationEditorReady()) { return}
530       annotationEditor.setEditingEnabled(false);
531       // the first time we invoke previous action we want to go two
532       // previous matches back not just one
533       matchedIndexes.removeLast();
534 
535       Vector<Integer> v;
536       if (matchedIndexes.size() == 1) {
537         // no more previous annotation, disable the action
538         findPreviousAction.setEnabled(false);
539       }
540       v = matchedIndexes.getLast();
541       int start = v.firstElement();
542       int end = v.lastElement();
543       getOwner().getTextComponent().select(start, end);
544       annotationEditor.placeDialog(start, end);
545       // reset the matcher for the next FindNextAction
546       nextMatchStartsFrom = start;
547       findNextAction.setEnabled(true);
548       annotateMatchAction.setEnabled(true);
549     }
550   }
551 
552   protected class FindNextAction extends AbstractAction{
553     private static final long serialVersionUID = 1L;
554 
555     public FindNextAction(){
556       super("Next");
557       super.putValue(SHORT_DESCRIPTION, "Finds the next occurrence.");
558       super.putValue(MNEMONIC_KEY, KeyEvent.VK_N);
559     }
560 
561     public void actionPerformed(ActionEvent evt){
562       if (!isAnnotationEditorReady()) { return}
563       annotationEditor.setEditingEnabled(false);
564       boolean found = false;
565       int start = -1;
566       int end = -1;
567       nextMatchStartsFrom = getOwner().getTextComponent().getCaretPosition();
568 
569       while (matcher.find(nextMatchStartsFrom&& !found) {
570         start = (matcher.groupCount()>0)?matcher.start(1):matcher.start();
571         end = (matcher.groupCount()>0)?matcher.end(1):matcher.end();
572         found = false;
573         if (searchHighlightsChk.isSelected()) {
574           javax.swing.text.Highlighter.Highlight[] highlights =
575             getOwner().getTextComponent().getHighlighter().getHighlights();
576           for (javax.swing.text.Highlighter.Highlight h : highlights) {
577             if (h.getStartOffset() <= start && h.getEndOffset() >= end) {
578               found = true;
579               break;
580             }
581           }
582         else {
583           found = true;
584         }
585         nextMatchStartsFrom = end;
586       }
587 
588       if (found) {
589         Vector<Integer> v = new Vector<Integer>(2);
590         v.add(start);
591         v.add(end);
592         matchedIndexes.add(v);
593         getOwner().getTextComponent().select(start, end);
594         annotationEditor.placeDialog(start, end);
595         findPreviousAction.setEnabled(true);
596       else {
597         //no more matches possible
598         findNextAction.setEnabled(false);
599         annotateMatchAction.setEnabled(false);
600       }
601     }
602   }
603   
604   protected class AnnotateMatchAction extends AbstractAction{
605     private static final long serialVersionUID = 1L;
606 
607     public AnnotateMatchAction(){
608       super("Annotate");
609       super.putValue(SHORT_DESCRIPTION, "Annotates the current match.");
610       super.putValue(MNEMONIC_KEY, KeyEvent.VK_A);
611     }
612     
613     public void actionPerformed(ActionEvent evt){
614       if (!isAnnotationEditorReady()) { return}
615       int start = getOwner().getTextComponent().getSelectionStart();
616       int end = getOwner().getTextComponent().getSelectionEnd();
617       FeatureMap features = Factory.newFeatureMap();
618       if(annotationEditor.getAnnotationCurrentlyEdited().getFeatures() != null
619         features.putAll(annotationEditor.getAnnotationCurrentlyEdited().getFeatures());
620       try {
621         Integer id = annotationEditor.getAnnotationSetCurrentlyEdited().add(
622           new Long(start)new Long(end),
623           annotationEditor.getAnnotationCurrentlyEdited().getType(), features);
624         Annotation newAnn =
625           annotationEditor.getAnnotationSetCurrentlyEdited().get(id);
626         getOwner().getTextComponent().select(end, end);
627         //set the annotation as selected
628         getOwner().selectAnnotation(new AnnotationDataImpl(
629                 annotationEditor.getAnnotationSetCurrentlyEdited(), newAnn));
630         annotationEditor.editAnnotation(newAnn,
631            annotationEditor.getAnnotationSetCurrentlyEdited());
632         annotateAllMatchesAction.setEnabled(true);
633         if (annotateAllMatchesSmallButton.getAction()
634               .equals(undoAnnotateAllMatchesAction)) {
635           annotateAllMatchesSmallButton.setAction(annotateAllMatchesAction);
636         }
637       }
638       catch(InvalidOffsetException e) {
639         //the offsets here should always be valid.
640         throw new LuckyException(e);
641       }
642     }
643   }
644   
645   protected class AnnotateAllMatchesAction extends AbstractAction{
646     private static final long serialVersionUID = 1L;
647 
648     public AnnotateAllMatchesAction(){
649       super("Ann. all next");
650       super.putValue(SHORT_DESCRIPTION, "Annotates all the following matches.");
651       super.putValue(MNEMONIC_KEY, KeyEvent.VK_L);
652     }
653 
654     public void actionPerformed(ActionEvent evt){
655       if (!isAnnotationEditorReady()) { return}
656       annotateAllAnnotationsID = new LinkedList<Annotation>();
657       boolean found = false;
658       int start = -1;
659       int end = -1;
660       nextMatchStartsFrom =
661         getOwner().getTextComponent().getCaretPosition();
662  
663       do {
664       found = false;
665       while (matcher.find(nextMatchStartsFrom&& !found) {
666         start = (matcher.groupCount()>0)?matcher.start(1):matcher.start();
667         end = (matcher.groupCount()>0)?matcher.end(1):matcher.end();
668         if (searchHighlightsChk.isSelected()) {
669           javax.swing.text.Highlighter.Highlight[] highlights =
670             getOwner().getTextComponent().getHighlighter().getHighlights();
671           for (javax.swing.text.Highlighter.Highlight h : highlights) {
672             if (h.getStartOffset() <= start && h.getEndOffset() >= end) {
673               found = true;
674               break;
675             }
676           }
677         else {
678           found = true;
679         }
680         nextMatchStartsFrom = end;
681       }
682       if (found) { annotateCurrentMatch(start, end)}
683       while (found && !matcher.hitEnd());
684 
685       annotateAllMatchesSmallButton.setAction(undoAnnotateAllMatchesAction);
686       undoAnnotateAllMatchesAction.setEnabled(true);
687     }
688 
689     private void annotateCurrentMatch(int start, int end){
690         FeatureMap features = Factory.newFeatureMap();
691         features.put("safe.regex""true");
692         if(annotationEditor.getAnnotationCurrentlyEdited().getFeatures() != null
693           features.putAll(annotationEditor.getAnnotationCurrentlyEdited().getFeatures());
694         try {
695           Integer id = annotationEditor.getAnnotationSetCurrentlyEdited().add(
696             new Long(start)new Long(end),
697             annotationEditor.getAnnotationCurrentlyEdited().getType(),
698             features);
699           Annotation newAnn =
700             annotationEditor.getAnnotationSetCurrentlyEdited().get(id);
701           annotateAllAnnotationsID.add(newAnn);
702         }
703         catch(InvalidOffsetException e) {
704           //the offsets here should always be valid.
705           throw new LuckyException(e);
706         }
707     }
708   }
709   
710   /**
711    * Remove the annotations added by the last action that annotate all matches.
712    */
713   protected class UndoAnnotateAllMatchesAction extends AbstractAction{
714     private static final long serialVersionUID = 1L;
715 
716     public UndoAnnotateAllMatchesAction(){
717       super("Undo");
718       super.putValue(SHORT_DESCRIPTION, "Undo previous annotate all action.");
719       super.putValue(MNEMONIC_KEY, KeyEvent.VK_U);
720     }
721     
722     public void actionPerformed(ActionEvent evt){
723 
724       for(Annotation annotation : annotateAllAnnotationsID) {
725         annotationEditor.getAnnotationSetCurrentlyEdited().remove(annotation);
726       }
727 
728       if (annotationEditor.getAnnotationSetCurrentlyEdited() == null) {
729         annotationEditor.setEditingEnabled(false);
730       }
731 
732       annotateAllMatchesSmallButton.setAction(annotateAllMatchesAction);
733       annotateAllMatchesAction.setEnabled(false);
734     }
735   }
736 
737   /**
738    * A smaller JButton with less margins.
739    */
740   protected class SmallButton extends JButton{
741     private static final long serialVersionUID = 1L;
742 
743     public SmallButton(Action a) {
744       super(a);
745       setMargin(new Insets(0202));
746     }
747   }
748 
749   /**
750    @return the owner
751    */
752   public AnnotationEditorOwner getOwner() {
753     return annotationEditor.getOwner();
754   }
755 
756 }