SearchAndAnnotatePanel.java
001 /*
002  *  Copyright (c) 1995-2012, 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           @Override
250           public void actionPerformed(ActionEvent arg0) {
251             findFirstAction.actionPerformed(null);
252           }
253         });
254       hBox.add(searchTextField);
255       hBox.add(Box.createHorizontalStrut(2));
256       helpRegExpButton = new JButton("?");
257       helpRegExpButton.setMargin(new Insets(0202));
258       helpRegExpButton.setToolTipText("GATE search expression builder.");
259       helpRegExpButton.addActionListener(new SearchExpressionsAction(
260         searchTextField, annotationEditorWindow, searchRegExpChk));
261       hBox.add(helpRegExpButton);
262       hBox.add(Box.createHorizontalGlue());
263     searchPane.add(hBox);
264       hBox = Box.createHorizontalBox();
265       hBox.add(Box.createHorizontalStrut(5));
266         findFirstAction = new FindFirstAction();
267         firstSmallButton = new SmallButton(findFirstAction);
268       hBox.add(firstSmallButton);
269       hBox.add(Box.createHorizontalStrut(5));
270         findPreviousAction = new FindPreviousAction();
271         findPreviousAction.setEnabled(false);
272       hBox.add(new SmallButton(findPreviousAction));
273       hBox.add(Box.createHorizontalStrut(5));
274         findNextAction = new FindNextAction();
275         findNextAction.setEnabled(false);
276       hBox.add(new SmallButton(findNextAction));
277       hBox.add(Box.createHorizontalStrut(5));
278         annotateMatchAction = new AnnotateMatchAction();
279         annotateMatchAction.setEnabled(false);
280       hBox.add(new SmallButton(annotateMatchAction));
281       hBox.add(Box.createHorizontalStrut(5));
282         annotateAllMatchesAction = new AnnotateAllMatchesAction();
283         undoAnnotateAllMatchesAction = new UndoAnnotateAllMatchesAction();
284         annotateAllMatchesSmallButton =
285           new SmallButton(annotateAllMatchesAction);
286         annotateAllMatchesAction.setEnabled(false);
287         undoAnnotateAllMatchesAction.setEnabled(false);
288       hBox.add(annotateAllMatchesSmallButton);
289       hBox.add(Box.createHorizontalStrut(5));
290     searchPane.add(hBox);
291     searchBox.add(searchPane);
292 
293     mainPane.add(searchBox);
294   }
295 
296   protected void initListeners() {
297 
298     searchEnabledCheck.addActionListener(new ActionListener() {
299       @Override
300       public void actionPerformed(ActionEvent e) {
301         if (searchEnabledCheck.isSelected()) {
302           //add the search box if not already there
303           if (!searchBox.isAncestorOf(searchPane)) {
304             // if empty, initialise the search field to the text
305             // of the current annotation
306             String searchText = searchTextField.getText()
307             if (searchText == null || searchText.trim().length() == 0) {
308               if (annotationEditor.getAnnotationCurrentlyEdited() != null
309                 && getOwner() != null) {
310                 String annText = getOwner().getDocument().getContent().
311                     toString().substring(
312                       annotationEditor.getAnnotationCurrentlyEdited()
313                         .getStartNode().getOffset().intValue(),
314                       annotationEditor.getAnnotationCurrentlyEdited()
315                         .getEndNode().getOffset().intValue());
316                 searchTextField.setText(annText);
317               }
318             }
319             searchBox.add(searchPane);
320           }
321           searchEnabledCheck.setText("");
322           searchCaseSensChk.setVisible(true);
323           searchRegExpChk.setVisible(true);
324           searchWholeWordsChk.setVisible(true);
325           searchHighlightsChk.setVisible(true);
326           searchTextField.requestFocusInWindow();
327           searchTextField.selectAll();
328           annotationEditorWindow.pack();
329           annotationEditor.setPinnedMode(true);
330 
331         else {
332           if(searchBox.isAncestorOf(searchPane)){
333             searchEnabledCheck.setText("Open Search & Annotate tool");
334             searchBox.remove(searchPane);
335             searchCaseSensChk.setVisible(false);
336             searchRegExpChk.setVisible(false);
337             searchWholeWordsChk.setVisible(false);
338             searchHighlightsChk.setVisible(false);
339             if (annotationEditor.getAnnotationCurrentlyEdited() != null) {
340               annotationEditor.setEditingEnabled(true);
341             }
342             annotationEditorWindow.pack();
343           }
344         }
345       }
346     });
347 
348     this.addAncestorListener(new AncestorListener() {
349       @Override
350       public void ancestorAdded(AncestorEvent event) {
351         // put the selection of the document into the search text field
352         if (searchTextField.getText().trim().length() == 0
353           && getOwner().getTextComponent().getSelectedText() != null) {
354           searchTextField.setText(getOwner().getTextComponent().getSelectedText());
355         }
356       }
357       @Override
358       public void ancestorRemoved(AncestorEvent event) {
359         // if the editor window is closed
360         enableActions(false);
361       }
362       @Override
363       public void ancestorMoved(AncestorEvent event) {
364         // do nothing
365       }
366     });
367 
368     searchTextField.getDocument().addDocumentListener(new DocumentListener(){
369       @Override
370       public void changedUpdate(DocumentEvent e) {
371         enableActions(false);
372       }
373       @Override
374       public void insertUpdate(DocumentEvent e) {
375         enableActions(false);
376       }
377       @Override
378       public void removeUpdate(DocumentEvent e) {
379         enableActions(false);
380       }
381     });
382 
383     searchCaseSensChk.addActionListener(new ActionListener() {
384       @Override
385       public void actionPerformed(ActionEvent e) {
386         enableActions(false);
387       }
388     });
389 
390     searchRegExpChk.addActionListener(new ActionListener() {
391       @Override
392       public void actionPerformed(ActionEvent e) {
393         enableActions(false);
394       }
395     });
396 
397     searchWholeWordsChk.addActionListener(new ActionListener() {
398       @Override
399       public void actionPerformed(ActionEvent e) {
400         enableActions(false);
401       }
402     });
403 
404       searchHighlightsChk.addActionListener(new ActionListener() {
405       @Override
406       public void actionPerformed(ActionEvent e) {
407         enableActions(false);
408       }
409     });
410 
411   }
412 
413   private void enableActions(boolean state){
414     findPreviousAction.setEnabled(state);
415     findNextAction.setEnabled(state);
416     annotateMatchAction.setEnabled(state);
417     annotateAllMatchesAction.setEnabled(state);
418     if (annotateAllMatchesSmallButton.getAction()
419             .equals(undoAnnotateAllMatchesAction)) {
420       annotateAllMatchesSmallButton.setAction(annotateAllMatchesAction);
421     }
422   }
423 
424   private boolean isAnnotationEditorReady() {
425     if (!annotationEditor.editingFinished()
426      || getOwner() == null
427      || annotationEditor.getAnnotationCurrentlyEdited() == null
428      || annotationEditor.getAnnotationSetCurrentlyEdited() == null) {
429       annotationEditorWindow.setVisible(false);
430       JOptionPane.showMessageDialog(annotationEditorWindow,
431         (annotationEditor.getAnnotationCurrentlyEdited() == null?
432           "Please select an existing annotation\n"
433           "or create a new one then select it."
434           :
435           "Please set all required features in the feature table."),
436         "GATE",
437         JOptionPane.INFORMATION_MESSAGE);
438       annotationEditorWindow.setVisible(true);
439       return false;
440     else {
441       return true
442     }
443   }
444 
445   protected class FindFirstAction extends AbstractAction{
446     private static final long serialVersionUID = 1L;
447 
448     public FindFirstAction(){
449       super("First");
450       super.putValue(SHORT_DESCRIPTION, "Finds the first occurrence.");
451       super.putValue(MNEMONIC_KEY, KeyEvent.VK_F);
452     }
453 
454     @Override
455     public void actionPerformed(ActionEvent evt){
456       if (!isAnnotationEditorReady()) { return}
457       annotationEditor.setPinnedMode(true);
458       annotationEditor.setEditingEnabled(false);
459       String patternText = searchTextField.getText();
460       Pattern pattern;
461 
462       try {
463         String prefixPattern = searchWholeWordsChk.isSelected() "\\b":"";
464         prefixPattern += searchRegExpChk.isSelected() "":"\\Q";
465         String suffixPattern = searchRegExpChk.isSelected() "":"\\E";
466         suffixPattern += searchWholeWordsChk.isSelected() "\\b":"";
467         patternText = prefixPattern + patternText + suffixPattern;
468         // TODO: Pattern.UNICODE_CASE prevent insensitive case to work
469         // for Java 1.5 but works with Java 1.6
470         pattern = searchCaseSensChk.isSelected() ?
471                   Pattern.compile(patternText:
472                   Pattern.compile(patternText, Pattern.CASE_INSENSITIVE);
473 
474       catch(PatternSyntaxException e) {
475         // hides the annotator window
476         // to be able to see the dialog window
477         annotationEditorWindow.setVisible(false);
478         JOptionPane.showMessageDialog(annotationEditorWindow,
479           "Invalid regular expression.\n\n"
480           + e.toString().replaceFirst("^.+PatternSyntaxException: """),
481           "GATE",
482           JOptionPane.INFORMATION_MESSAGE);
483         annotationEditorWindow.setVisible(true);
484         return;
485       }
486 
487       matcher = pattern.matcher(content);
488       boolean found = false;
489       int start = -1;
490       int end = -1;
491       nextMatchStartsFrom = 0;
492       while (matcher.find(nextMatchStartsFrom&& !found) {
493         start = (matcher.groupCount()>0)?matcher.start(1):matcher.start();
494         end = (matcher.groupCount()>0)?matcher.end(1):matcher.end();
495         found = false;
496         if (searchHighlightsChk.isSelected()) {
497           javax.swing.text.Highlighter.Highlight[] highlights =
498             getOwner().getTextComponent().getHighlighter().getHighlights();
499           for (javax.swing.text.Highlighter.Highlight h : highlights) {
500             if (h.getStartOffset() <= start && h.getEndOffset() >= end) {
501               found = true;
502               break;
503             }
504           }
505         else {
506           found = true;
507         }
508         nextMatchStartsFrom = end;
509       }
510 
511       if (found) {
512         findNextAction.setEnabled(true);
513         annotateMatchAction.setEnabled(true);
514         annotateAllMatchesAction.setEnabled(false);
515         matchedIndexes = new LinkedList<Vector<Integer>>();
516         Vector<Integer> v = new Vector<Integer>(2);
517         v.add(start);
518         v.add(end);
519         matchedIndexes.add(v);
520         getOwner().getTextComponent().select(start, end);
521         annotationEditor.placeDialog(start, end);
522 
523       else {
524         // no match found
525         findNextAction.setEnabled(false);
526         annotateMatchAction.setEnabled(false);
527       }
528       findPreviousAction.setEnabled(false);
529     }
530   }
531   
532   protected class FindPreviousAction extends AbstractAction {
533     private static final long serialVersionUID = 1L;
534 
535     public FindPreviousAction() {
536       super("Prev.");
537       super.putValue(SHORT_DESCRIPTION, "Finds the previous occurrence.");
538       super.putValue(MNEMONIC_KEY, KeyEvent.VK_P);
539     }
540 
541     @Override
542     public void actionPerformed(ActionEvent evt) {
543       if (!isAnnotationEditorReady()) { return}
544       annotationEditor.setEditingEnabled(false);
545       // the first time we invoke previous action we want to go two
546       // previous matches back not just one
547       matchedIndexes.removeLast();
548 
549       Vector<Integer> v;
550       if (matchedIndexes.size() == 1) {
551         // no more previous annotation, disable the action
552         findPreviousAction.setEnabled(false);
553       }
554       v = matchedIndexes.getLast();
555       int start = v.firstElement();
556       int end = v.lastElement();
557       getOwner().getTextComponent().select(start, end);
558       annotationEditor.placeDialog(start, end);
559       // reset the matcher for the next FindNextAction
560       nextMatchStartsFrom = start;
561       findNextAction.setEnabled(true);
562       annotateMatchAction.setEnabled(true);
563     }
564   }
565 
566   protected class FindNextAction extends AbstractAction{
567     private static final long serialVersionUID = 1L;
568 
569     public FindNextAction(){
570       super("Next");
571       super.putValue(SHORT_DESCRIPTION, "Finds the next occurrence.");
572       super.putValue(MNEMONIC_KEY, KeyEvent.VK_N);
573     }
574 
575     @Override
576     public void actionPerformed(ActionEvent evt){
577       if (!isAnnotationEditorReady()) { return}
578       annotationEditor.setEditingEnabled(false);
579       boolean found = false;
580       int start = -1;
581       int end = -1;
582       nextMatchStartsFrom = getOwner().getTextComponent().getCaretPosition();
583 
584       while (matcher.find(nextMatchStartsFrom&& !found) {
585         start = (matcher.groupCount()>0)?matcher.start(1):matcher.start();
586         end = (matcher.groupCount()>0)?matcher.end(1):matcher.end();
587         found = false;
588         if (searchHighlightsChk.isSelected()) {
589           javax.swing.text.Highlighter.Highlight[] highlights =
590             getOwner().getTextComponent().getHighlighter().getHighlights();
591           for (javax.swing.text.Highlighter.Highlight h : highlights) {
592             if (h.getStartOffset() <= start && h.getEndOffset() >= end) {
593               found = true;
594               break;
595             }
596           }
597         else {
598           found = true;
599         }
600         nextMatchStartsFrom = end;
601       }
602 
603       if (found) {
604         Vector<Integer> v = new Vector<Integer>(2);
605         v.add(start);
606         v.add(end);
607         matchedIndexes.add(v);
608         getOwner().getTextComponent().select(start, end);
609         annotationEditor.placeDialog(start, end);
610         findPreviousAction.setEnabled(true);
611       else {
612         //no more matches possible
613         findNextAction.setEnabled(false);
614         annotateMatchAction.setEnabled(false);
615       }
616     }
617   }
618   
619   protected class AnnotateMatchAction extends AbstractAction{
620     private static final long serialVersionUID = 1L;
621 
622     public AnnotateMatchAction(){
623       super("Annotate");
624       super.putValue(SHORT_DESCRIPTION, "Annotates the current match.");
625       super.putValue(MNEMONIC_KEY, KeyEvent.VK_A);
626     }
627     
628     @Override
629     public void actionPerformed(ActionEvent evt){
630       if (!isAnnotationEditorReady()) { return}
631       int start = getOwner().getTextComponent().getSelectionStart();
632       int end = getOwner().getTextComponent().getSelectionEnd();
633       FeatureMap features = Factory.newFeatureMap();
634       if(annotationEditor.getAnnotationCurrentlyEdited().getFeatures() != null
635         features.putAll(annotationEditor.getAnnotationCurrentlyEdited().getFeatures());
636       try {
637         Integer id = annotationEditor.getAnnotationSetCurrentlyEdited().add(
638           new Long(start)new Long(end),
639           annotationEditor.getAnnotationCurrentlyEdited().getType(), features);
640         Annotation newAnn =
641           annotationEditor.getAnnotationSetCurrentlyEdited().get(id);
642         getOwner().getTextComponent().select(end, end);
643         //set the annotation as selected
644         getOwner().selectAnnotation(new AnnotationDataImpl(
645                 annotationEditor.getAnnotationSetCurrentlyEdited(), newAnn));
646         annotationEditor.editAnnotation(newAnn,
647            annotationEditor.getAnnotationSetCurrentlyEdited());
648         annotateAllMatchesAction.setEnabled(true);
649         if (annotateAllMatchesSmallButton.getAction()
650               .equals(undoAnnotateAllMatchesAction)) {
651           annotateAllMatchesSmallButton.setAction(annotateAllMatchesAction);
652         }
653       }
654       catch(InvalidOffsetException e) {
655         //the offsets here should always be valid.
656         throw new LuckyException(e);
657       }
658     }
659   }
660   
661   protected class AnnotateAllMatchesAction extends AbstractAction{
662     private static final long serialVersionUID = 1L;
663 
664     public AnnotateAllMatchesAction(){
665       super("Ann. all next");
666       super.putValue(SHORT_DESCRIPTION, "Annotates all the following matches.");
667       super.putValue(MNEMONIC_KEY, KeyEvent.VK_L);
668     }
669 
670     @Override
671     public void actionPerformed(ActionEvent evt){
672       if (!isAnnotationEditorReady()) { return}
673       annotateAllAnnotationsID = new LinkedList<Annotation>();
674       boolean found = false;
675       int start = -1;
676       int end = -1;
677       nextMatchStartsFrom =
678         getOwner().getTextComponent().getCaretPosition();
679  
680       do {
681       found = false;
682       while (matcher.find(nextMatchStartsFrom&& !found) {
683         start = (matcher.groupCount()>0)?matcher.start(1):matcher.start();
684         end = (matcher.groupCount()>0)?matcher.end(1):matcher.end();
685         if (searchHighlightsChk.isSelected()) {
686           javax.swing.text.Highlighter.Highlight[] highlights =
687             getOwner().getTextComponent().getHighlighter().getHighlights();
688           for (javax.swing.text.Highlighter.Highlight h : highlights) {
689             if (h.getStartOffset() <= start && h.getEndOffset() >= end) {
690               found = true;
691               break;
692             }
693           }
694         else {
695           found = true;
696         }
697         nextMatchStartsFrom = end;
698       }
699       if (found) { annotateCurrentMatch(start, end)}
700       while (found && !matcher.hitEnd());
701 
702       annotateAllMatchesSmallButton.setAction(undoAnnotateAllMatchesAction);
703       undoAnnotateAllMatchesAction.setEnabled(true);
704     }
705 
706     private void annotateCurrentMatch(int start, int end){
707         FeatureMap features = Factory.newFeatureMap();
708         features.put("safe.regex""true");
709         if(annotationEditor.getAnnotationCurrentlyEdited().getFeatures() != null
710           features.putAll(annotationEditor.getAnnotationCurrentlyEdited().getFeatures());
711         try {
712           Integer id = annotationEditor.getAnnotationSetCurrentlyEdited().add(
713             new Long(start)new Long(end),
714             annotationEditor.getAnnotationCurrentlyEdited().getType(),
715             features);
716           Annotation newAnn =
717             annotationEditor.getAnnotationSetCurrentlyEdited().get(id);
718           annotateAllAnnotationsID.add(newAnn);
719         }
720         catch(InvalidOffsetException e) {
721           //the offsets here should always be valid.
722           throw new LuckyException(e);
723         }
724     }
725   }
726   
727   /**
728    * Remove the annotations added by the last action that annotate all matches.
729    */
730   protected class UndoAnnotateAllMatchesAction extends AbstractAction{
731     private static final long serialVersionUID = 1L;
732 
733     public UndoAnnotateAllMatchesAction(){
734       super("Undo");
735       super.putValue(SHORT_DESCRIPTION, "Undo previous annotate all action.");
736       super.putValue(MNEMONIC_KEY, KeyEvent.VK_U);
737     }
738     
739     @Override
740     public void actionPerformed(ActionEvent evt){
741 
742       for(Annotation annotation : annotateAllAnnotationsID) {
743         annotationEditor.getAnnotationSetCurrentlyEdited().remove(annotation);
744       }
745 
746       if (annotationEditor.getAnnotationSetCurrentlyEdited() == null) {
747         annotationEditor.setEditingEnabled(false);
748       }
749 
750       annotateAllMatchesSmallButton.setAction(annotateAllMatchesAction);
751       annotateAllMatchesAction.setEnabled(false);
752     }
753   }
754 
755   /**
756    * A smaller JButton with less margins.
757    */
758   protected class SmallButton extends JButton{
759     private static final long serialVersionUID = 1L;
760 
761     public SmallButton(Action a) {
762       super(a);
763       setMargin(new Insets(0202));
764     }
765   }
766 
767   /**
768    @return the owner
769    */
770   public AnnotationEditorOwner getOwner() {
771     return annotationEditor.getOwner();
772   }
773 
774 }