KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > netbeans > editor > Annotations


1 /*
2  * The contents of this file are subject to the terms of the Common Development
3  * and Distribution License (the License). You may not use this file except in
4  * compliance with the License.
5  *
6  * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
7  * or http://www.netbeans.org/cddl.txt.
8  *
9  * When distributing Covered Code, include this CDDL Header Notice in each file
10  * and include the License file at http://www.netbeans.org/cddl.txt.
11  * If applicable, add the following below the CDDL Header, with the fields
12  * enclosed by brackets [] replaced by your own identifying information:
13  * "Portions Copyrighted [year] [name of copyright owner]"
14  *
15  * The Original Software is NetBeans. The Initial Developer of the Original
16  * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
17  * Microsystems, Inc. All Rights Reserved.
18  */

19
20 package org.netbeans.editor;
21
22 import java.util.LinkedList JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import org.netbeans.editor.BaseDocument;
25 import org.netbeans.editor.DrawLayerFactory;
26 import javax.swing.text.BadLocationException JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.ArrayList JavaDoc;
29 import javax.swing.event.DocumentListener JavaDoc;
30 import org.netbeans.editor.BaseDocumentEvent;
31 import javax.swing.event.DocumentEvent JavaDoc;
32 import javax.swing.event.EventListenerList JavaDoc;
33 import java.util.EventListener JavaDoc;
34 import java.beans.PropertyChangeListener JavaDoc;
35 import java.beans.PropertyChangeEvent JavaDoc;
36 import java.util.Comparator JavaDoc;
37 import org.netbeans.editor.AnnotationDesc;
38 import org.netbeans.editor.AnnotationTypes;
39 import javax.swing.JPopupMenu JavaDoc;
40 import javax.swing.Action JavaDoc;
41 import java.util.Map JavaDoc;
42 import java.util.TreeSet JavaDoc;
43 import javax.swing.JMenu JavaDoc;
44 import javax.swing.JMenuItem JavaDoc;
45 import javax.swing.SwingUtilities JavaDoc;
46 import org.netbeans.editor.ext.ExtKit;
47 import org.openide.util.NbBundle;
48 import org.openide.util.RequestProcessor;
49 import org.openide.util.actions.Presenter;
50
51 /** Annotations class act as data model containing all annotations attached
52  * to one document. Class uses instances of private class LineAnnotations for
53  * grouping of added annotations by line. These objects (LineAnnotations) are
54  * referenced from two collections. First one is Map where the key is Mark.
55  * It is used during the drawing in DrawLayerFactory.AnnotationLayer - when
56  * the mark appears in mark change, the LineAnnotations instance is found for
57  * it and the active annotation on the line can be queried.
58  * Second is List where the LineAnnotations are sorted by line number. This
59  * list is used for drawing the annotations in the gutter when the sequential
60  * order is important.
61  *
62  * The class also listen on document. It need to know how many lines where
63  * removed or added to refresh the LineAnnotations.line property.
64  *
65  * @author David Konecny
66  * @since 07/2001
67  */

68 public class Annotations implements DocumentListener JavaDoc {
69     
70     /** Map of [Mark, LineAnnotations] */
71     private HashMap JavaDoc lineAnnotationsByMark;
72     
73     /** List of [LineAnnotations] which is ordered by line number */
74     private ArrayList JavaDoc lineAnnotationsArray;
75
76     /** Drawing layer for drawing of annotations */
77     private DrawLayerFactory.AnnotationLayer drawLayer;
78
79     /** Reference to document */
80     private BaseDocument doc;
81
82     /** List of listeners on AnnotationsListener*/
83     private EventListenerList JavaDoc listenerList;
84
85     /** Property change listener on annotation type changes */
86     private PropertyChangeListener JavaDoc l;
87     
88     /** Property change listener on AnnotationTypes changes */
89     private PropertyChangeListener JavaDoc annoTypesListener;
90
91     /** Whether the column with glyph icons is visible */
92     private boolean glyphColumn = false;
93     
94     /** Whether the column with cycling button is visible*/
95     private boolean glyphButtonColumn = false;
96     
97     /** Whether the gutter popup menu has been initialized */
98     private boolean menuInitialized = false;
99
100     /** Sorts the subMenu items */
101     public static final Comparator JavaDoc MENU_COMPARATOR = new MenuComparator();
102
103     public Annotations(BaseDocument doc) {
104         lineAnnotationsByMark = new HashMap JavaDoc(30);
105         lineAnnotationsArray = new ArrayList JavaDoc(20);
106         listenerList = new EventListenerList JavaDoc();
107         
108         drawLayer = null;
109         this.doc = doc;
110
111         // add annotation drawing layer
112
doc.addLayer(new DrawLayerFactory.AnnotationLayer(doc),
113                 DrawLayerFactory.ANNOTATION_LAYER_VISIBILITY);
114
115         // listener on document changes
116
this.doc.addDocumentListener(this);
117         
118         l = new PropertyChangeListener JavaDoc() {
119             public void propertyChange (PropertyChangeEvent JavaDoc evt) {
120                 if (evt.getPropertyName() == AnnotationDesc.PROP_ANNOTATION_TYPE) {
121                     AnnotationDesc anno = (AnnotationDesc)evt.getSource();
122                     LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(anno.getMark());
123                     lineAnnos.refreshAnnotations();
124                     refreshLine(lineAnnos.getLine());
125                 }
126                 if (evt.getPropertyName() == AnnotationDesc.PROP_MOVE_TO_FRONT) {
127                     AnnotationDesc anno = (AnnotationDesc)evt.getSource();
128                     frontAnnotation(anno);
129                 }
130             }
131         };
132
133         AnnotationTypes.getTypes().addPropertyChangeListener( annoTypesListener = new PropertyChangeListener JavaDoc() {
134             public void propertyChange (PropertyChangeEvent JavaDoc evt) {
135                 if (evt.getPropertyName() == AnnotationTypes.PROP_COMBINE_GLYPHS) {
136                     LineAnnotations lineAnnos;
137                     for( Iterator JavaDoc it = lineAnnotationsArray.iterator(); it.hasNext(); ) {
138                         lineAnnos = (LineAnnotations)it.next();
139                         lineAnnos.refreshAnnotations();
140                     }
141                 }
142                 if (evt.getPropertyName() == AnnotationTypes.PROP_ANNOTATION_TYPES) {
143                     LineAnnotations lineAnnos;
144                     for( Iterator JavaDoc it = lineAnnotationsArray.iterator(); it.hasNext(); ) {
145                         lineAnnos = (LineAnnotations)it.next();
146                         for( Iterator JavaDoc it2 = lineAnnos.getAnnotations(); it2.hasNext(); ) {
147                             AnnotationDesc anno = (AnnotationDesc)it2.next();
148                             anno.updateAnnotationType();
149                         }
150                     }
151                 }
152                 fireChangedAll();
153             }
154         });
155         
156     }
157
158     /** Finds the drawing layer for annotations */
159     public synchronized DrawLayerFactory.AnnotationLayer getLayer() {
160         if (drawLayer == null)
161             drawLayer = (DrawLayerFactory.AnnotationLayer)doc.findLayer(DrawLayerFactory.ANNOTATION_LAYER_NAME);
162         return drawLayer;
163     }
164     
165     /** Add annotation */
166     public void addAnnotation(AnnotationDesc anno) {
167
168         // Should always be run in EQ
169
if (!SwingUtilities.isEventDispatchThread()) {
170             throw new IllegalStateException JavaDoc("Must be run in EQ"); // NOI18N
171
}
172
173         // create mark for this annotation. One mark can be shared by more annotations
174
MarkChain chain = getLayer().getMarkChain();
175         try {
176             /* Always adds a fresh mark. It helps to behave well
177              * in the following scenario:
178              * 1. add annotaion at line e.g. 3.
179              * 2. add annotation at line 4.
180              * 3. goto begining of line 2.
181              * 4. select lines 2,3,4,5 by pressing down arrow.
182              * 5. remove selected lines.
183              * 6. Undo by ctrl-z.
184              * If reusing marks this will result into line selection
185              * being at line 2 but gutter marking of annotations
186              * being at line 4.
187              * It happens because although DocumentLine.updatePositionRef()
188              * removes and adds the annotations on modified lines, it does that
189              * sequentially for each line so it removes
190              * first of the two DocumentLine's annotations
191              * and tries to readd it to begining of removed block
192              * but it finds there an existing mark
193              * from second DocumentLine's annotation.
194              * It reuses the mark but as the mark was inside
195              * the removed block originally the undo restores its position
196              * inside the block.
197              * Creation of fresh marks fixes that problem.
198              */

199             chain.addMark(anno.getOffset(), true);
200
201         } catch (BadLocationException JavaDoc e) {
202             /*
203              * This is problematic point.
204              * Once this place is reached the annotation desc
205              * will have null mark and will
206              * not in fact be actively present
207              * in the document.
208              * Such annotation will then throw NPE
209              * in removeAnnotation().
210              * Hopefully causes of getting to this place
211              * were eliminated.
212              */

213             throw new IllegalStateException JavaDoc("offset=" + anno.getOffset() // NOI18N
214
+ ", docLen=" + doc.getLength()); // NOI18N
215
}
216         // attach created mark to annotation
217
MarkFactory.ChainDrawMark annoMark = chain.getAddedMark();
218         if (annoMark == null) {
219             throw new NullPointerException JavaDoc();
220         }
221         anno.setMark(annoMark);
222
223         // #33165 - different strategy - first trying to search
224
// by the mark in the map [mark, lineAnnos]. That should
225
// eliminate problems when a mark is tried to be removed
226
// from the mark chain a second time.
227
// Hopefully this will not cause any other sorts of problems.
228

229         LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(annoMark);
230         if (lineAnnos == null) {
231             // fine LineAnnotations instance corresponding to the line of this annotation
232
// or create new LineAnnotations if this is first annotation on this line
233
lineAnnos = getLineAnnotations(anno.getLine());
234             if (lineAnnos == null) {
235                 lineAnnos = new LineAnnotations();
236                 lineAnnos.addAnnotation(anno);
237                 if (lineAnnotationsByMark.put(anno.getMark(), lineAnnos) != null) {
238                     throw new IllegalStateException JavaDoc("Mark already in the map."); // NOI18N
239
}
240
241                 // insert newly created LineAnnotations into sorted array
242
boolean inserted = false;
243                 for (int i=0; i < lineAnnotationsArray.size(); i++) {
244                     if (((LineAnnotations)lineAnnotationsArray.get(i)).getLine() > lineAnnos.getLine()) {
245                         lineAnnotationsArray.add(i, lineAnnos);
246                         inserted = true;
247                         break;
248                     }
249                 }
250                 if (!inserted)
251                         lineAnnotationsArray.add(lineAnnos);
252
253             }
254             else {
255                 lineAnnos.addAnnotation(anno);
256                 // check whether this mark is in lineAnnotationsByMark Map
257
// it is possible that Line.Part annotations will have more marks
258
// for one line
259
if (lineAnnotationsByMark.get(anno.getMark()) == null)
260                     lineAnnotationsByMark.put(anno.getMark(), lineAnnos);
261             }
262             
263         } else { // mark was already in lineAnnotationsByMark map
264
lineAnnos.addAnnotation(anno);
265         }
266
267         // add listener on changes of annotation type
268
anno.addPropertyChangeListener(l);
269
270         // ignore annotation types with default icon
271
if (anno.isVisible() && (!anno.isDefaultGlyph() || (anno.isDefaultGlyph() && lineAnnos.getCount() > 1))) {
272             glyphColumn = true;
273         }
274         
275         if (lineAnnos.getCount() > 1)
276             glyphButtonColumn = true;
277
278         // notify view that it must be redrawn
279
refreshLine(lineAnnos.getLine());
280
281 // System.out.println("AFTER ADD:\n" + dumpLineAnnotationsArray());
282
}
283
284     /** Remove annotation */
285     public void removeAnnotation(AnnotationDesc anno) {
286
287         // Should always be run in EQ
288
if (!SwingUtilities.isEventDispatchThread()) {
289             throw new IllegalStateException JavaDoc("Must be run in EQ"); // NOI18N
290
}
291
292         // find LineAnnotations for the mark
293
MarkFactory.ChainDrawMark annoMark = (MarkFactory.ChainDrawMark)anno.getMark();
294         if (annoMark == null) {
295             throw new NullPointerException JavaDoc();
296         }
297
298         LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(annoMark);
299
300         int line = lineAnnos.getLine();
301         // remove annotation from the line
302
lineAnnos.removeAnnotation(anno);
303         
304         // check if this mark is referenced or not. If not, remove it
305
if (!lineAnnos.isMarkStillReferenced(annoMark)) {
306             if (lineAnnotationsByMark.remove(annoMark) != lineAnnos) {
307                 throw new IllegalStateException JavaDoc();
308             }
309
310             MarkChain chain = getLayer().getMarkChain();
311             // Partial fix of #33165 - rather remove mark explicitly than through its offset
312
//chain.removeMark(anno.getOffset());
313
if (!chain.removeMark(annoMark)) {
314                 throw new IllegalStateException JavaDoc("Mark not removed"); // NOI18N
315
}
316         }
317         
318         // if there is no more annotations on the line, remove LineAnnotations
319
if (lineAnnos.getCount() == 0) {
320             int index = lineAnnotationsArray.indexOf(lineAnnos);
321             // #90220 - Sometimes there seems to be an inconsistency that the lineAnnos
322
// is not present in the array.
323
if (index != -1) {
324                 lineAnnotationsArray.remove(index);
325             }
326         }
327
328         // clear the mark from annotation
329
anno.setMark(null);
330
331         // remove listener on changes of annotation type
332
anno.removePropertyChangeListener(l);
333
334         // notify view that must be redrawn
335
refreshLine(line);
336 // System.out.println("AFTER REMOVE:\n" + dumpLineAnnotationsArray());
337
}
338     
339     /** Finds active annotation for the Mark. It is called from DrawLayer
340      * when it found the DrawMark */

341     public AnnotationDesc getActiveAnnotation(Mark mark) {
342         LineAnnotations annos;
343         annos = (LineAnnotations)lineAnnotationsByMark.get(mark);
344         if (annos == null) {
345             return null;
346         }
347         AnnotationDesc anno = annos.getActive();
348         // it is possible that some other mark on the line (means
349
// some other annotations) is active
350
if (anno==null || anno.getMark() != mark) {
351             return null;
352         }
353         return anno;
354     }
355
356     /** Returns the active annotation for the line given by Mark. */
357     AnnotationDesc getLineActiveAnnotation(Mark mark) {
358         LineAnnotations annos;
359         annos = (LineAnnotations)lineAnnotationsByMark.get(mark);
360         if (annos == null) {
361             return null;
362         }
363         AnnotationDesc anno = annos.getActive();
364         return anno;
365     }
366     
367     /** Finds LineAnnotations for the given line number */
368     protected LineAnnotations getLineAnnotations(int line) {
369         LineAnnotations annos;
370         // since fix of #33165 it is possible to use binary search here
371
int low = 0;
372         int high = lineAnnotationsArray.size() - 1;
373         while (low <= high) {
374             int mid = (low + high) / 2;
375             annos = (LineAnnotations)lineAnnotationsArray.get(mid);
376             int annosLine = annos.getLine();
377             if (line < annosLine) {
378                 high = mid - 1;
379             } else if (line > annosLine) {
380                 low = mid + 1;
381             } else {
382                 return annos;
383             }
384         }
385         return null;
386     }
387
388     /** Returns the active annotation for the given line number.
389      * It is called from the glyph gutter*/

390     public AnnotationDesc getActiveAnnotation(int line) {
391         LineAnnotations annos = getLineAnnotations(line);
392         if (annos == null)
393             return null;
394         return annos.getActive();
395     }
396
397     /** Move annotation in front of others. The activated annotation
398      * is moved in front of other annotations on the same line */

399     public void frontAnnotation(AnnotationDesc anno) {
400         int line = anno.getLine();
401         LineAnnotations annos = getLineAnnotations(line);
402         if (annos == null)
403             return;
404         annos.activate(anno);
405         refreshLine(line);
406     }
407
408     /** Activate next annotation on the line. Used for cycling
409      * through the annotations */

410     public AnnotationDesc activateNextAnnotation(int line) {
411         LineAnnotations annos = getLineAnnotations(line);
412         if (annos == null)
413             return null;
414         AnnotationDesc aa = annos.activateNext();
415         refreshLine(line);
416         return aa;
417     }
418
419     /** Get next line number with some annotation*/
420     public int getNextLineWithAnnotation(int line) {
421         LineAnnotations annos;
422
423         // since fix of #33165 it is possible to use binary search here
424
int low = 0;
425         int high = lineAnnotationsArray.size() - 1;
426         while (low <= high) {
427             int mid = (low + high) / 2;
428             annos = (LineAnnotations)lineAnnotationsArray.get(mid);
429             int annosLine = annos.getLine();
430             if (line < annosLine) {
431                 high = mid - 1;
432             } else if (line > annosLine) {
433                 low = mid + 1;
434             } else {
435                 return annosLine;
436             }
437         }
438         
439         // not found annotation on exact line, follow the search with simple searching
440
for (int i=low; i<lineAnnotationsArray.size(); i++) {
441             annos = (LineAnnotations)lineAnnotationsArray.get(i);
442             int annosLine = annos.getLine();
443             if (annosLine >= line){
444                 return annosLine;
445             }
446         }
447         
448         return -1;
449     }
450
451     /** Get next line number with some annotation*/
452     public AnnotationDesc getAnnotation(int line, String JavaDoc type) {
453         return null;
454     }
455     
456     /** Return list of pasive annotations which should be drawn on the backgorund */
457     public AnnotationDesc[] getPasiveAnnotations(int line) {
458         LineAnnotations annos = getLineAnnotations(line);
459         if (annos == null)
460             return null;
461         if (annos.getCount() <= 1)
462             return null;
463         return annos.getPasive();
464     }
465     
466     /** Return list of passive annotations attached on the line of given offset */
467     public AnnotationDesc[] getPassiveAnnotations(int offset){
468         int lineIndex = doc.getDefaultRootElement().getElementIndex(offset);
469         return (lineIndex>=0) ? getPasiveAnnotations(lineIndex) : null;
470     }
471
472     /** Returns number of visible annotations on the line*/
473     public int getNumberOfAnnotations(int line) {
474         LineAnnotations annos = getLineAnnotations(line);
475         if (annos == null)
476             return 0;
477         return annos.getCount();
478     }
479     
480
481     /** Notify view that it is necessary to redraw the line of the document */
482     protected void refreshLine(int line) {
483         fireChangedLine(line);
484         int start = Utilities.getRowStartFromLineOffset(doc, line);
485         int end = Utilities.getRowStartFromLineOffset(doc, line+1);
486         if (end == -1)
487             end = doc.getLength();
488         doc.repaintBlock(start, end);
489     }
490     
491     /** Checks the number of removed lines and recalculate
492      * LineAnnotations.line property */

493     public void removeUpdate(DocumentEvent e) {
494         BaseDocumentEvent be = (BaseDocumentEvent)e;
495         int countOfDeletedLines = be.getLFCount();
496         if (countOfDeletedLines == 0)
497             return;
498         
499         int changedLine = be.getLine();
500
501 /* #33165 - line in line-annotations handled in a different way
502         LineAnnotations annos;
503         for (int i=0; i<lineAnnotationsArray.size(); i++) {
504             annos = (LineAnnotations)lineAnnotationsArray.get(i);
505             if (annos.getLine() > changedLine && annos.getLine() < changedLine+countOfDeletedLines)
506                 annos.setLine(changedLine);
507             if (annos.getLine() > changedLine)
508                 annos.setLine(annos.getLine()-countOfDeletedLines);
509         }
510  */

511         // fire event to AnnotationsListeners that everything should be redraw
512
fireChangedAll();
513     }
514     
515     /** Checks the number of inserted lines and recalculate
516      * LineAnnotations.line property */

517     public void insertUpdate(DocumentEvent e) {
518         BaseDocumentEvent be = (BaseDocumentEvent)e;
519         int countOfInsertedLines = be.getLFCount();
520         if (countOfInsertedLines == 0)
521             return;
522         
523 /* #33165 - line in line-annotations handled in a different way
524         int changedLine = be.getLine();
525
526         LineAnnotations annos;
527         LineAnnotations current = null;
528         for (int i=0; i<lineAnnotationsArray.size(); i++) {
529             annos = (LineAnnotations)lineAnnotationsArray.get(i);
530             if (annos.getLine() == changedLine && annos.getActive().getOffset() > e.getOffset())
531                 current = annos;
532             if (annos.getLine() > changedLine)
533                 annos.setLine(annos.getLine()+countOfInsertedLines);
534         }
535         if (current != null)
536             current.setLine(current.getLine()+countOfInsertedLines);
537  */

538         
539         // fire event to AnnotationsListeners that everything should be redraw
540
fireChangedAll();
541     }
542     
543     /**Gives notification that an attribute or set of attributes changed.*/
544     public void changedUpdate(DocumentEvent e) {
545     }
546     
547     /** Add AnnotationsListener listener */
548     public void addAnnotationsListener(AnnotationsListener listener) {
549     listenerList.add(AnnotationsListener.class, listener);
550     }
551
552     /** Remove AnnotationsListener listener */
553     public void removeAnnotationsListener(AnnotationsListener listener) {
554     listenerList.remove(AnnotationsListener.class, listener);
555     }
556
557     /** Fire AnnotationsListener.ChangedLine change*/
558     protected void fireChangedLine(int line) {
559     // Guaranteed to return a non-null array
560
Object JavaDoc[] listeners = listenerList.getListenerList();
561     // Process the listeners last to first, notifying
562
// those that are interested in this event
563
for (int i = listeners.length-2; i>=0; i-=2) {
564         if (listeners[i]==AnnotationsListener.class) {
565         // Lazily create the event:
566
// if (e == null)
567
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
568
((AnnotationsListener)listeners[i+1]).changedLine(line);
569         }
570     }
571     }
572    
573     /** Fire AnnotationsListener.ChangedAll change*/
574     protected void fireChangedAll() {
575     // Guaranteed to return a non-null array
576
Object JavaDoc[] listeners = listenerList.getListenerList();
577     // Process the listeners last to first, notifying
578
// those that are interested in this event
579
for (int i = listeners.length-2; i>=0; i-=2) {
580         if (listeners[i]==AnnotationsListener.class) {
581         // Lazily create the event:
582
// if (e == null)
583
// e = new ListSelectionEvent(this, firstIndex, lastIndex);
584
((AnnotationsListener)listeners[i+1]).changedAll();
585         }
586     }
587     }
588
589     /** Return whether this document has or had any glyph icon attached.
590      * This method is called from glyph gutter to check whether the glyph column
591      * should be drawn or not. */

592     public boolean isGlyphColumn() {
593         return glyphColumn;
594     }
595     
596     /** Return whether this document has or had more annotations on one line.
597      * This method is called from glyph gutter to check whether the glyph cycling
598      * column should be drawn or not. */

599     public boolean isGlyphButtonColumn() {
600         return glyphButtonColumn;
601     }
602
603     
604     private void addAcceleretors(Action JavaDoc a, JMenuItem JavaDoc item, BaseKit kit){
605         // Try to get the accelerator
606
javax.swing.text.JTextComponent JavaDoc target = Utilities.getFocusedComponent();
607         if (target == null) return;
608         javax.swing.text.Keymap JavaDoc km = target.getKeymap();
609         if (km != null) {
610             javax.swing.KeyStroke JavaDoc[] keys = km.getKeyStrokesForAction(a);
611             if (keys != null && keys.length > 0) {
612                 item.setAccelerator(keys[0]);
613             }else{
614                 // retrieve via actionName
615
String JavaDoc actionName = (String JavaDoc)a.getValue(Action.NAME);
616                 if (actionName == null) return;
617                 BaseAction action = (BaseAction)kit.getActionByName(actionName);
618                 if (action == null) return;
619                 keys = km.getKeyStrokesForAction(action);
620                 if (keys != null && keys.length > 0) {
621                     item.setAccelerator(keys[0]);
622                 }
623             }
624         }
625     }
626     
627     /** Creates menu item for the given action. It must handle the BaseActions, which
628      * have localized name stored not in Action.NAME property. */

629     private JMenuItem JavaDoc createMenuItem(Action JavaDoc action, BaseKit kit) {
630         JMenuItem JavaDoc item;
631         if (action instanceof BaseAction) {
632             item = new JMenuItem JavaDoc( ((BaseAction)action).getPopupMenuText(null) );
633             item.addActionListener(action);
634             addAcceleretors(action, item, kit);
635         } else if (action instanceof Presenter.Popup) {
636             item = ((Presenter.Popup) action).getPopupPresenter();
637         } else {
638             item = new JMenuItem JavaDoc( (String JavaDoc)action.getValue(Action.NAME) );
639             item.addActionListener(action);
640             addAcceleretors(action, item, kit);
641         }
642         return item;
643     }
644
645     /** Creates popup menu with all actions for the given line. */
646     public JPopupMenu JavaDoc createPopupMenu(BaseKit kit, int line) {
647         return createMenu(kit, line, false).getPopupMenu();
648     }
649     
650     private void initMenu(JMenu JavaDoc pm, BaseKit kit, int line){
651         LineAnnotations annos = getLineAnnotations(line);
652         Map JavaDoc types = new HashMap JavaDoc(AnnotationTypes.getTypes().getVisibleAnnotationTypeNamesCount() * 4/3);
653
654         Action JavaDoc[] actions;
655         boolean separator = false;
656         boolean added = false;
657         JMenu JavaDoc subMenu;
658         TreeSet JavaDoc orderedSubMenus = new TreeSet JavaDoc(MENU_COMPARATOR);
659
660         if (annos != null) {
661             
662             // first, add actions for active annotation
663
AnnotationDesc anno = annos.getActive();
664             if (anno != null) {
665                 actions = anno.getActions();
666                 if (actions != null) {
667                     subMenu = new JMenu JavaDoc(anno.getAnnotationTypeInstance().getDescription());
668                     for (int j=0; j<actions.length; j++)
669                         subMenu.add(createMenuItem(actions[j], kit));
670                     
671                     if (subMenu.getItemCount() > 0) {
672                         orderedSubMenus.add(subMenu);
673                         separator = true;
674                     }
675                     types.put(anno.getAnnotationType(), anno.getAnnotationType());
676                 }
677             }
678
679             // second, add submenus for all pasive annotations
680
AnnotationDesc[] pasiveAnnos = annos.getPasive();
681             added = false;
682             if (pasiveAnnos != null) {
683                 for (int i=0; i < pasiveAnnos.length; i++) {
684                     actions = pasiveAnnos[i].getActions();
685                     if (actions != null) {
686                         subMenu = new JMenu JavaDoc(pasiveAnnos[i].getAnnotationTypeInstance().getDescription());
687                         for (int j=0; j<actions.length; j++)
688                             subMenu.add(createMenuItem(actions[j], kit));
689                         if (separator) {
690                             separator = false;
691                             //pm.addSeparator();
692
}
693                         if (subMenu.getItemCount() > 0){
694                             //pm.add(subMenu);
695
orderedSubMenus.add(subMenu);
696                             added = true;
697                         }
698                         types.put(pasiveAnnos[i].getAnnotationType(), pasiveAnnos[i].getAnnotationType());
699                     }
700                 }
701                 if (added)
702                     separator = true;
703             }
704         }
705
706         // third, add all remaining possible actions to the end of the list
707
added = false;
708         AnnotationType type;
709         for (Iterator JavaDoc i = AnnotationTypes.getTypes().getAnnotationTypeNames(); i.hasNext(); ) {
710             type = AnnotationTypes.getTypes().getType((String JavaDoc)i.next());
711             if (type == null || !type.isVisible())
712                 continue;
713             if (types.get(type.getName()) != null)
714                 continue;
715             actions = type.getActions();
716             if (actions != null) {
717                 subMenu = new JMenu JavaDoc(type.getDescription());
718                 for (int j=0; j<actions.length; j++) {
719                     if (actions[j].isEnabled()) {
720                         subMenu.add(createMenuItem(actions[j], kit));
721                     }
722                 }
723                 if (separator) {
724                     separator = false;
725                     //pm.addSeparator();
726
}
727                 if (subMenu.getItemCount() > 0){
728                     //pm.add(subMenu);
729
orderedSubMenus.add(subMenu);
730                     added = true;
731                 }
732             }
733         }
734         
735         if (added)
736             separator = true;
737
738         /*
739         if (separator)
740             pm.addSeparator();
741          */

742         
743         if (!orderedSubMenus.isEmpty()){
744             Iterator JavaDoc iter = orderedSubMenus.iterator();
745             while(iter.hasNext()){
746                 subMenu = (JMenu JavaDoc) iter.next();
747                 pm.add(subMenu);
748             }
749             pm.addSeparator();
750         }
751         
752         
753         // add checkbox for enabling/disabling of line numbers
754
BaseAction action = (BaseAction)kit.getActionByName(BaseKit.toggleLineNumbersAction);
755         pm.add(action.getPopupMenuItem(null));
756         
757         BaseAction action2 = (BaseAction)kit.getActionByName(ExtKit.toggleToolbarAction);
758         if (action2 != null){
759             pm.add(action2.getPopupMenuItem(null));
760         }
761         menuInitialized = true;
762     }
763
764     private static class DelayedMenu extends JMenu JavaDoc{
765         
766         RequestProcessor.Task task;
767         
768         public DelayedMenu(String JavaDoc s){
769             super(s);
770         }
771         
772         public JPopupMenu JavaDoc getPopupMenu() {
773             RequestProcessor.Task t = task;
774             if (t!=null && !t.isFinished()){
775                 t.waitFinished();
776             }
777             return super.getPopupMenu();
778         }
779         
780         void setTask(RequestProcessor.Task task){
781             this.task = task;
782         }
783         
784         void clearTask() {
785             this.task = null; // clear the finished task to avoid leaking
786
}
787     }
788     
789     private JMenu JavaDoc createMenu(BaseKit kit, int line, boolean backgroundInit){
790         final DelayedMenu pm = new DelayedMenu(NbBundle.getBundle(BaseKit.class).getString("generate-gutter-popup"));
791         final BaseKit fKit = kit;
792         final int fLine = line;
793
794         if (backgroundInit){
795             RequestProcessor rp = RequestProcessor.getDefault();
796             RequestProcessor.Task task = rp.create(new Runnable JavaDoc(){
797                 public void run(){
798                     initMenu(pm, fKit, fLine);
799                     pm.clearTask(); // clear the finished task reference to avoid leaking
800
}
801             });
802             pm.setTask(task); // set before task execution so that always cleaned properly
803
task.schedule(0);
804         }else{
805             initMenu(pm, fKit, fLine);
806         }
807
808         return pm;
809     }
810     
811     /** Creates popup menu with all actions for the given line. */
812     public JMenu JavaDoc createMenu(BaseKit kit, int line) {
813         boolean bkgInit = menuInitialized;
814         menuInitialized = true;
815         return createMenu(kit, line, !bkgInit);
816     }
817     
818     private String JavaDoc dumpAnnotaionDesc(AnnotationDesc ad) {
819         return "offset=" + ad.getOffset() // NOI18N
820
+ "(ls=" + doc.getParagraphElement(ad.getOffset()).getStartOffset() // NOI18N
821
+ "), line=" + ad.getLine() // NOI18N
822
+ ", type=" + ad.getAnnotationType(); // NOI18N
823
}
824
825     private String JavaDoc dumpLineAnnotationsArray() {
826         StringBuffer JavaDoc sb = new StringBuffer JavaDoc();
827         for (int i = 0; i < lineAnnotationsArray.size(); i++) {
828             LineAnnotations la = (LineAnnotations)lineAnnotationsArray.get(i);
829             LinkedList JavaDoc annos = la.annos;
830             sb.append("[" + i + "]: line=" + la.getLine() // NOI18N
831
+ ", anos:"); // NOI18N
832
for (int j = 0; j < annos.size(); j++) {
833                 sb.append("\n [" + j + "]: " + dumpAnnotaionDesc((AnnotationDesc)annos.get(j))); // NOI18N
834
}
835             sb.append('\n');
836         }
837         return sb.toString();
838     }
839             
840     
841     /** Manager of all annotations attached to one line. Class stores
842      * the references to all annotations from one line in List and also
843      * stores which annotation is active, count of visible annotations
844      * and line number. */

845     static public class LineAnnotations extends Object JavaDoc {
846
847         /** List with all annotations in this LineAnnotations */
848         private LinkedList JavaDoc annos;
849
850         /** List with all visible annotations in this LineAnnotations */
851         private LinkedList JavaDoc annosVisible;
852         
853         /** Active annotation. Used only in case there is more than one
854          * annotation on the line */

855         private AnnotationDesc active;
856         
857         /** Line number */
858 // private int lineNumber;
859

860         protected LineAnnotations() {
861             annos = new LinkedList JavaDoc();
862             annosVisible = new LinkedList JavaDoc();
863 // lineNumber = -1;
864
}
865
866         /** Add annotation to this line and activate it. */
867         public void addAnnotation(AnnotationDesc anno) {
868 // if (lineNumber == -1)
869
// lineNumber = anno.getLine();
870
annos.add(anno);
871             if (anno.isVisible()) {
872                 active = anno;
873             }
874             refreshAnnotations();
875         }
876         
877         /** Remove annotation from this line. Refresh the active one
878          * and count of visible. */

879         public void removeAnnotation(AnnotationDesc anno) {
880             if (anno == active)
881                 activateNext();
882             annos.remove(anno);
883             if (active == anno)
884                 active = null;
885             refreshAnnotations();
886         }
887
888         /** Return the active line annotation. */
889         public AnnotationDesc getActive() {
890             return active;
891         }
892
893         /** Getter for the line number property */
894         public int getLine() {
895             // #33165 - delegating of getting of the line number to first anno
896
return (annos.size() > 0)
897                 ? ((AnnotationDesc)annos.get(0)).getLine()
898                 : 0;
899 // return lineNumber;
900
}
901
902         /** Setter for the line number property */
903         public void setLine(int line) {
904 // lineNumber = line;
905
throw new IllegalStateException JavaDoc("Setting of line number not allowed"); // NOI18N
906
}
907
908         /** Gets the array of all pasive and visible annotations */
909         public AnnotationDesc[] getPasive() {
910             AnnotationDesc[] pasives = new AnnotationDesc[getCount()-1];
911             int startIndex = annosVisible.indexOf(getActive());
912             int index = startIndex;
913             int i=0;
914             while (true) {
915                 index++;
916                 if (index >= annosVisible.size())
917                     index = 0;
918                 if (index == startIndex)
919                     break;
920
921                 pasives[i] = (AnnotationDesc)annosVisible.get(index);
922                 i++;
923             }
924             return pasives;
925         }
926
927         /** Make the given annotation active. */
928         public boolean activate(AnnotationDesc anno) {
929             
930             int i,j;
931             i = annosVisible.indexOf(anno);
932
933             if (i == -1) {
934                 // was anno combined by some type ??
935
for(j=0; j < annosVisible.size(); j++) {
936                     if (annosVisible.get(j) instanceof AnnotationCombination) {
937                         if (((AnnotationCombination)annosVisible.get(j)).isAnnotationCombined(anno)) {
938                             i = j;
939                             anno = (AnnotationCombination)annosVisible.get(j);
940                             break;
941                         }
942                     }
943                 }
944             }
945             
946             if (i == -1)
947                 return false;
948             
949             if (annosVisible.get(i) == null)
950                 return false;
951             
952             if (anno == active || !anno.isVisible())
953                 return false;
954             
955             active = anno;
956             
957             return true;
958         }
959
960         /** Get count of visible annotations on the line */
961         public int getCount() {
962             return annosVisible.size();
963         }
964
965         /** Activate next annoation on the line. Used during the cycling. */
966         public AnnotationDesc activateNext() {
967             if (getCount() <= 1)
968                 return active;
969             
970             int current = annosVisible.indexOf(active);
971             current++;
972             if (current >= getCount())
973                 current = 0;
974             active = (AnnotationDesc)annosVisible.get(current);
975             return active;
976         }
977         
978         /** Searches all combination annotation type and sort them
979          * by getCombinationOrder into combTypes array
980          * which is passed as paramter. */

981         private void fillInCombinationsAndOrderThem(LinkedList JavaDoc combTypes) {
982             AnnotationType type;
983             AnnotationType.CombinationMember[] combs;
984             
985             for (Iterator JavaDoc it = AnnotationTypes.getTypes().getAnnotationTypeNames(); it.hasNext(); ) {
986                 type = AnnotationTypes.getTypes().getType((String JavaDoc)it.next());
987                 if (type == null)
988                     continue;
989                 combs = type.getCombinations();
990                 if (combs != null && type.isWholeLine() &&
991                     (combs.length >= 2 || (combs.length == 1 && combs[0].isAbsorbAll())) ) {
992                     if (type.getCombinationOrder() == 0) {
993                         combTypes.add(type);
994                     } else {
995                         boolean inserted = false;
996                         for (int i=0; i < combTypes.size(); i++) {
997                             if ( ((AnnotationType)combTypes.get(i)).getCombinationOrder() > type.getCombinationOrder()) {
998                                 combTypes.add(i, type);
999                                 inserted = true;
1000                                break;
1001                            }
1002                        }
1003                        if (!inserted)
1004                            combTypes.add(type);
1005                    }
1006                }
1007            }
1008        }
1009
1010        /** For the given combination annotation type and list of annotations
1011         * it finds all annotations which are combined by this combination
1012         * and inserts into list of annotations new combined annotation which
1013         * wraps combined annotations. The result list of annotations can
1014         * contain null values for annotations which were combined. */

1015        private boolean combineType(AnnotationType combType, LinkedList JavaDoc annosDupl) {
1016
1017            int i, j, k;
1018            boolean matchedType;
1019            int countOfAnnos = 0;
1020            int valid_optional_count = 0;
1021            
1022            LinkedList JavaDoc combinedAnnos = new LinkedList JavaDoc();
1023
1024            AnnotationType.CombinationMember[] combs = combType.getCombinations();
1025            
1026            // check that there is match between line annos & all types specified in combination
1027
boolean matchedComb = true;
1028            AnnotationType.CombinationMember comb;
1029            AnnotationDesc anno;
1030            for (i=0; i < combs.length; i++) {
1031
1032                comb = combs[i];
1033                matchedType = false;
1034                
1035                // check that for one specified combination type there exist some annotation
1036
for (j=0; j < annosDupl.size(); j++) {
1037                    
1038                    anno = (AnnotationDesc)annosDupl.get(j);
1039                    
1040                    if (anno == null)
1041                        continue;
1042
1043                    // check whether this annotation matches the specified combination type
1044
if (comb.getName().equals( anno.getAnnotationType() )) {
1045                        countOfAnnos++;
1046
1047                        // now check if the combination has specified some minimum count of annos
1048
if (comb.getMinimumCount() == 0) {
1049                            matchedType = true;
1050                            countOfAnnos++;
1051                            combinedAnnos.add(anno);
1052                            if (!comb.isAbsorbAll())
1053                                break;
1054                        } else {
1055                            int requiredCount = comb.getMinimumCount() - 1;
1056                            for (k=j+1; (k < annosDupl.size()) && (requiredCount > 0); k++) {
1057                                if (annosDupl.get(k) == null)
1058                                    continue;
1059                                if (comb.getName().equals( ((AnnotationDesc)annosDupl.get(k)).getAnnotationType() )) {
1060                                    requiredCount--;
1061                                }
1062                            }
1063                            if (requiredCount == 0) {
1064                                matchedType = true;
1065                                
1066                                combinedAnnos.add(anno);
1067                                for (k=j+1; k < annosDupl.size(); k++) {
1068                                    if (annosDupl.get(k) == null)
1069                                        continue;
1070                                    if (comb.getName().equals( ((AnnotationDesc)annosDupl.get(k)).getAnnotationType() )) {
1071                                        countOfAnnos++;
1072                                        combinedAnnos.add(annosDupl.get(k));
1073                                    }
1074                                }
1075                            }
1076                            break;
1077                        }
1078                        
1079                    }
1080                    
1081                }
1082                
1083                if (matchedType) {
1084                    if (comb.isOptional())
1085                        valid_optional_count++;
1086                } else {
1087                    if (!comb.isOptional()) {
1088                        matchedComb = false;
1089                        break;
1090                    }
1091                }
1092                
1093            }
1094            if (combType.getMinimumOptionals() > valid_optional_count)
1095                matchedComb = false;
1096
1097            AnnotationCombination annoComb = null;
1098            if (matchedComb) {
1099
1100                boolean activateComb = false;
1101                
1102                for (i=0; i<combinedAnnos.size(); i++) {
1103                    if (combinedAnnos.get(i) == active)
1104                        activateComb = true;
1105                    
1106                    if (annoComb == null) {
1107                        annoComb = new AnnotationCombination(combType.getName(), (AnnotationDesc)combinedAnnos.get(i));
1108                        annosDupl.set(annosDupl.indexOf(combinedAnnos.get(i)),annoComb); // replace the original annotation by the new Combined one
1109
} else {
1110                        annoComb.addCombinedAnnotation((AnnotationDesc)combinedAnnos.get(i));
1111                        annosDupl.set(annosDupl.indexOf(combinedAnnos.get(i)),null); // remove annotations which were combined form the array
1112
}
1113                }
1114                if (activateComb)
1115                    active = annoComb;
1116                
1117                return true;
1118            }
1119            
1120            return false;
1121        }
1122    
1123        
1124        
1125        
1126        /** Refresh the active annotation and count of visible annotations.
1127         * This method is used after change of annotation type of some annotation
1128         * on this line */

1129        public void refreshAnnotations() {
1130            int i;
1131            
1132            if (!AnnotationTypes.getTypes().isCombineGlyphs().booleanValue()) {
1133                
1134                // combinations are disabled
1135
annosVisible = new LinkedList JavaDoc();
1136                for (i=0; i < annos.size(); i++) {
1137                    if ( ! ((AnnotationDesc)annos.get(i)).isVisible() )
1138                        continue;
1139                    annosVisible.add(annos.get(i));
1140                }
1141                
1142            } else {
1143                
1144                // combination are enabled
1145
LinkedList JavaDoc annosDupl = (LinkedList JavaDoc)annos.clone();
1146    
1147                // List of all annotation types
1148
LinkedList JavaDoc combTypes = new LinkedList JavaDoc();
1149                
1150                // first, fill in the array with combination types sorted by the order
1151
fillInCombinationsAndOrderThem(combTypes);
1152                
1153                for (int ct=0; ct < combTypes.size(); ct++) {
1154                    combineType((AnnotationType)combTypes.get(ct), annosDupl);
1155                }
1156
1157                annosVisible = new LinkedList JavaDoc();
1158
1159                // add remaining not combined annotations into the line annotations array
1160
for (i=0; i < annosDupl.size(); i++) {
1161                    if (annosDupl.get(i) != null && ((AnnotationDesc)annosDupl.get(i)).isVisible() )
1162                        annosVisible.add(annosDupl.get(i));
1163                }
1164            }
1165
1166            // update the active annotation
1167
if (annosVisible.indexOf(active) == -1) {
1168                if (annosVisible.size() > 0)
1169                    active = (AnnotationDesc)annosVisible.get(0);
1170                else
1171                    active = null;
1172            }
1173        }
1174
1175        /** Is this given mark still referenced by some annotation or it
1176         * can be removed from the draw mark chain */

1177        public boolean isMarkStillReferenced(Mark mark) {
1178            AnnotationDesc anno;
1179            for( Iterator JavaDoc it = annos.listIterator(); it.hasNext(); ) {
1180                anno = (AnnotationDesc)it.next();
1181                if (anno.getMark() == mark)
1182                   return true;
1183            }
1184            return false;
1185        }
1186        
1187        public Iterator JavaDoc getAnnotations() {
1188            return annos.iterator();
1189        }
1190        
1191    }
1192    
1193
1194    /** Listener for listening on changes in Annotations object.*/
1195    public interface AnnotationsListener extends EventListener JavaDoc {
1196
1197        /** This method is fired when annotations on the line are changed -
1198         * annotation was added, removed, changed, etc. */

1199        public void changedLine(int Line);
1200
1201        /** It is not possible to trace what have changed and so the listeners
1202         * are only informed that something has changed and that the change
1203         * must be reflected somehow (most probably by complete redraw). */

1204        public void changedAll();
1205
1206    }
1207
1208    /** Annotation which is used for representation of combined annotations.
1209     * Some basic operations like getLine etc. are delegated to one of the
1210     * annotations which are representd by this combined annotation. The only
1211     * added functionality is for tooltip text and annotation type.
1212     */

1213    private static class AnnotationCombination extends AnnotationDesc {
1214        
1215        /** Delegate annotaiton */
1216        private AnnotationDesc delegate;
1217
1218        /** Annotation type */
1219        private String JavaDoc type;
1220        
1221        /** List of annotations which are combined */
1222        private LinkedList JavaDoc list;
1223        
1224        public AnnotationCombination(String JavaDoc type, AnnotationDesc delegate) {
1225            super(delegate.getOffset(), delegate.getLength());
1226            this.delegate = delegate;
1227            this.type = type;
1228            updateAnnotationType();
1229            list = new LinkedList JavaDoc();
1230            list.add(delegate);
1231        }
1232        
1233        /** Getter for offset of this annotation */
1234        public int getOffset() {
1235            return delegate.getOffset();
1236        }
1237        
1238        /** Getter for line number of this annotation */
1239        public int getLine() {
1240            return delegate.getLine();
1241        }
1242        
1243        /** Getter for localized tooltip text for this annotation */
1244        public String JavaDoc getShortDescription() {
1245            return getAnnotationTypeInstance().getDescription();
1246        }
1247        
1248        /** Getter for annotation type name */
1249        public String JavaDoc getAnnotationType() {
1250            return type;
1251        }
1252
1253        /** Add the annotation to this combination */
1254        public void addCombinedAnnotation(AnnotationDesc anno) {
1255            list.add(anno);
1256        }
1257
1258        /** Is the given annotation part of this combination */
1259        public boolean isAnnotationCombined(AnnotationDesc anno) {
1260            if (list.indexOf(anno) == -1)
1261                return false;
1262            else
1263                return true;
1264        }
1265
1266        /** Get Mark which represent this annotation in document */
1267        Mark getMark() {
1268            return delegate.getMark();
1269        }
1270        
1271    }
1272
1273    public static final class MenuComparator implements Comparator JavaDoc {
1274
1275       
1276        public MenuComparator() {
1277        }
1278        
1279        public int compare(Object JavaDoc o1, Object JavaDoc o2) {
1280            JMenu JavaDoc menuOne = (JMenu JavaDoc)o1;
1281            JMenu JavaDoc menuTwo = (JMenu JavaDoc)o2;
1282            if (menuTwo == null || menuOne == null) return 0;
1283            String JavaDoc menuOneText = menuOne.getText();
1284            String JavaDoc menuTwoText = menuTwo.getText();
1285            if (menuTwoText == null || menuOneText == null) return 0;
1286            return menuOneText.compareTo(menuTwoText);
1287        }
1288
1289    }
1290    
1291}
1292
Popular Tags