/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.netbeans.editor; import org.netbeans.editor.ext.ExtKit; import javax.swing.*; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.event.EventListenerList; import javax.swing.text.BadLocationException; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.*; //cgs import org.openide.util.RequestProcessor; /** Annotations class act as data model containing all annotations attached * to one document. Class uses instances of private class LineAnnotations for * grouping of added annotations by line. These objects (LineAnnotations) are * referenced from two collections. First one is Map where the key is Mark. * It is used during the drawing in DrawLayerFactory.AnnotationLayer - when * the mark appears in mark change, the LineAnnotations instance is found for * it and the active annotation on the line can be queried. * Second is List where the LineAnnotations are sorted by line number. This * list is used for drawing the annotations in the gutter when the sequential * order is important. * * The class also listen on document. It need to know how many lines where * removed or added to refresh the LineAnnotations.line property. * * @author David Konecny * @since 07/2001 */ public class Annotations implements DocumentListener { /** Map of [Mark, LineAnnotations] */ private HashMap lineAnnotationsByMark; /** List of [LineAnnotations] which is ordered by line number */ private ArrayList lineAnnotationsArray; /** Drawing layer for drawing of annotations */ private DrawLayerFactory.AnnotationLayer drawLayer; /** Reference to document */ private BaseDocument doc; /** List of listeners on AnnotationsListener*/ private EventListenerList listenerList; /** Property change listener on annotation type changes */ private PropertyChangeListener l; /** Property change listener on AnnotationTypes changes */ private PropertyChangeListener annoTypesListener; /** Whether the column with glyph icons is visible */ private boolean glyphColumn = false; /** Whether the column with cycling button is visible*/ private boolean glyphButtonColumn = false; /** Whether the gutter popup menu has been initialized */ private boolean menuInitialized = false; public Annotations(BaseDocument doc) { lineAnnotationsByMark = new HashMap(30); lineAnnotationsArray = new ArrayList(20); listenerList = new EventListenerList(); drawLayer = null; this.doc = doc; // add annotation drawing layer doc.addLayer(new DrawLayerFactory.AnnotationLayer(doc), DrawLayerFactory.ANNOTATION_LAYER_VISIBILITY); // listener on document changes this.doc.addDocumentListener(this); l = new PropertyChangeListener() { public void propertyChange (PropertyChangeEvent evt) { if (evt.getPropertyName() == AnnotationDesc.PROP_ANNOTATION_TYPE) { AnnotationDesc anno = (AnnotationDesc)evt.getSource(); LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(anno.getMark()); lineAnnos.refreshAnnotations(); refreshLine(lineAnnos.getLine()); } if (evt.getPropertyName() == AnnotationDesc.PROP_MOVE_TO_FRONT) { AnnotationDesc anno = (AnnotationDesc)evt.getSource(); frontAnnotation(anno); } } }; AnnotationTypes.getTypes().addPropertyChangeListener( annoTypesListener = new PropertyChangeListener() { public void propertyChange (PropertyChangeEvent evt) { if (evt.getPropertyName() == AnnotationTypes.PROP_COMBINE_GLYPHS) { LineAnnotations lineAnnos; for( Iterator it = lineAnnotationsArray.iterator(); it.hasNext(); ) { lineAnnos = (LineAnnotations)it.next(); lineAnnos.refreshAnnotations(); } } if (evt.getPropertyName() == AnnotationTypes.PROP_ANNOTATION_TYPES) { LineAnnotations lineAnnos; for( Iterator it = lineAnnotationsArray.iterator(); it.hasNext(); ) { lineAnnos = (LineAnnotations)it.next(); for( Iterator it2 = lineAnnos.getAnnotations(); it2.hasNext(); ) { AnnotationDesc anno = (AnnotationDesc)it2.next(); anno.updateAnnotationType(); } } } fireChangedAll(); } }); } /** Finds the drawing layer for annotations */ public synchronized DrawLayerFactory.AnnotationLayer getLayer() { if (drawLayer == null) drawLayer = (DrawLayerFactory.AnnotationLayer)doc.findLayer(DrawLayerFactory.ANNOTATION_LAYER_NAME); return drawLayer; } /** Add annotation */ public void addAnnotation(AnnotationDesc anno) { // create mark for this annotation. One mark can be shared by more annotations MarkChain chain = getLayer().getMarkChain(); try { chain.addMark(anno.getOffset()); } catch (BadLocationException e) { return; } // attach created mark to annotation anno.setMark(chain.getAddedMark()); // fine LineAnnotations instance corresponding to the line of this annotation // or create new LineAnnotations if this is first annotation on this line LineAnnotations lineAnnos = getLineAnnotations(anno.getLine()); if (lineAnnos == null) { lineAnnos = new LineAnnotations(); lineAnnos.addAnnotation(anno); lineAnnotationsByMark.put(anno.getMark(), lineAnnos); // insert newly created LineAnnotations into sorted array boolean inserted = false; for (int i=0; i < lineAnnotationsArray.size(); i++) { if (((LineAnnotations)lineAnnotationsArray.get(i)).getLine() > lineAnnos.getLine()) { lineAnnotationsArray.add(i, lineAnnos); inserted = true; break; } } if (!inserted) lineAnnotationsArray.add(lineAnnos); } else { lineAnnos.addAnnotation(anno); // check whether this mark is in lineAnnotationsByMark Map // it is possible that Line.Part annotations will have more marks // for one line if (lineAnnotationsByMark.get(anno.getMark()) == null) lineAnnotationsByMark.put(anno.getMark(), lineAnnos); } // add listener on changes of annotation type anno.addPropertyChangeListener(l); // ignore annotation types with default icon if (anno.isVisible() && (!anno.isDefaultGlyph() || (anno.isDefaultGlyph() && lineAnnos.getCount() > 1))) { glyphColumn = true; } if (lineAnnos.getCount() > 1) glyphButtonColumn = true; // notify view that it must be redrawn refreshLine(lineAnnos.getLine()); } /** Remove annotation */ public void removeAnnotation(AnnotationDesc anno) { // find LineAnnotations for the mark LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(anno.getMark()); int line = lineAnnos.getLine(); // remove annotation from the line lineAnnos.removeAnnotation(anno); // check if this mark is referenced or not. If not, remove it if (!lineAnnos.isMarkStillReferenced(anno.getMark())) { lineAnnotationsByMark.remove(anno.getMark()); MarkChain chain = getLayer().getMarkChain(); chain.removeMark(anno.getOffset()); } // if there is no more annotations on the line, remove LineAnnotations if (lineAnnos.getCount() == 0) { lineAnnotationsArray.remove(lineAnnotationsArray.indexOf(lineAnnos)); } // clear the mark from annotation anno.setMark(null); // remove listener on changes of annotation type anno.removePropertyChangeListener(l); // notify view that must be redrawn refreshLine(line); } /** Finds active annotation for the Mark. It is called from DrawLayer * when it found the DrawMark */ public AnnotationDesc getActiveAnnotation(Mark mark) { LineAnnotations annos; annos = (LineAnnotations)lineAnnotationsByMark.get(mark); if (annos == null) { return null; } AnnotationDesc anno = annos.getActive(); // it is possible that some other mark on the line (means // some other annotations) is active if (anno.getMark() != mark) { return null; } return anno; } /** Returns the active annotation for the line given by Mark. */ AnnotationDesc getLineActiveAnnotation(Mark mark) { LineAnnotations annos; annos = (LineAnnotations)lineAnnotationsByMark.get(mark); if (annos == null) { return null; } AnnotationDesc anno = annos.getActive(); return anno; } /** Finds LineAnnotations for the given line number */ protected LineAnnotations getLineAnnotations(int line) { LineAnnotations annos; // TODO: optimize searching for (int i=0; i<lineAnnotationsArray.size(); i++) { annos = (LineAnnotations)lineAnnotationsArray.get(i); if (annos.getLine() == line) return annos; } return null; } /** Returns the active annotation for the given line number. * It is called from the glyph gutter*/ public AnnotationDesc getActiveAnnotation(int line) { LineAnnotations annos = getLineAnnotations(line); if (annos == null) return null; return annos.getActive(); } /** Move annotation in front of others. The activated annotation * is moved in front of other annotations on the same line */ public void frontAnnotation(AnnotationDesc anno) { int line = anno.getLine(); LineAnnotations annos = getLineAnnotations(line); if (annos == null) return; annos.activate(anno); refreshLine(line); } /** Activate next annotation on the line. Used for cycling * through the annotations */ public AnnotationDesc activateNextAnnotation(int line) { LineAnnotations annos = getLineAnnotations(line); if (annos == null) return null; AnnotationDesc aa = annos.activateNext(); refreshLine(line); return aa; } /** Get next line number with some annotation*/ public int getNextLineWithAnnotation(int line) { LineAnnotations annos; // TODO: optimize searching for (int i=0; i<lineAnnotationsArray.size(); i++) { annos = (LineAnnotations)lineAnnotationsArray.get(i); if (annos.getLine() >= line) return annos.getLine(); } return -1; } /** Get next line number with some annotation*/ public AnnotationDesc getAnnotation(int line, String type) { return null; } /** Return list of pasive annotations which should be drawn on the backgorund */ public AnnotationDesc[] getPasiveAnnotations(int line) { LineAnnotations annos = getLineAnnotations(line); if (annos == null) return null; if (annos.getCount() <= 1) return null; return annos.getPasive(); } /** Returns number of visible annotations on the line*/ public int getNumberOfAnnotations(int line) { LineAnnotations annos = getLineAnnotations(line); if (annos == null) return 0; return annos.getCount(); } /** Notify view that it is necessary to redraw the line of the document */ protected void refreshLine(int line) { fireChangedLine(line); int start = Utilities.getRowStartFromLineOffset(doc, line); int end = Utilities.getRowStartFromLineOffset(doc, line+1); if (end == -1) end = doc.getLength(); doc.repaintBlock(start, end); } /** Checks the number of removed lines and recalculate * LineAnnotations.line property */ public void removeUpdate(DocumentEvent e) { BaseDocumentEvent be = (BaseDocumentEvent)e; int countOfDeletedLines = be.getLFCount(); if (countOfDeletedLines == 0) return; int changedLine = be.getLine(); LineAnnotations annos; for (int i=0; i<lineAnnotationsArray.size(); i++) { annos = (LineAnnotations)lineAnnotationsArray.get(i); if (annos.getLine() > changedLine && annos.getLine() < changedLine+countOfDeletedLines) annos.setLine(changedLine); if (annos.getLine() > changedLine) annos.setLine(annos.getLine()-countOfDeletedLines); } // fire event to AnnotationsListeners that everything should be redraw fireChangedAll(); } /** Checks the number of inserted lines and recalculate * LineAnnotations.line property */ public void insertUpdate(DocumentEvent e) { BaseDocumentEvent be = (BaseDocumentEvent)e; int countOfInsertedLines = be.getLFCount(); if (countOfInsertedLines == 0) return; int changedLine = be.getLine(); LineAnnotations annos; LineAnnotations current = null; for (int i=0; i<lineAnnotationsArray.size(); i++) { annos = (LineAnnotations)lineAnnotationsArray.get(i); if (annos.getLine() == changedLine && annos.getActive().getOffset() > e.getOffset()) current = annos; if (annos.getLine() > changedLine) annos.setLine(annos.getLine()+countOfInsertedLines); } if (current != null) current.setLine(current.getLine()+countOfInsertedLines); // fire event to AnnotationsListeners that everything should be redraw fireChangedAll(); } /**Gives notification that an attribute or set of attributes changed.*/ public void changedUpdate(DocumentEvent e) { } /** Add AnnotationsListener listener */ public void addAnnotationsListener(AnnotationsListener listener) { listenerList.add(AnnotationsListener.class, listener); } /** Remove AnnotationsListener listener */ public void removeAnnotationsListener(AnnotationsListener listener) { listenerList.remove(AnnotationsListener.class, listener); } /** Fire AnnotationsListener.ChangedLine change*/ protected void fireChangedLine(int line) { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==AnnotationsListener.class) { // Lazily create the event: // if (e == null) // e = new ListSelectionEvent(this, firstIndex, lastIndex); ((AnnotationsListener)listeners[i+1]).changedLine(line); } } } /** Fire AnnotationsListener.ChangedAll change*/ protected void fireChangedAll() { // Guaranteed to return a non-null array Object[] listeners = listenerList.getListenerList(); // Process the listeners last to first, notifying // those that are interested in this event for (int i = listeners.length-2; i>=0; i-=2) { if (listeners[i]==AnnotationsListener.class) { // Lazily create the event: // if (e == null) // e = new ListSelectionEvent(this, firstIndex, lastIndex); ((AnnotationsListener)listeners[i+1]).changedAll(); } } } /** Return whether this document has or had any glyph icon attached. * This method is called from glyph gutter to check whether the glyph column * should be drawn or not. */ public boolean isGlyphColumn() { return glyphColumn; } /** Return whether this document has or had more annotations on one line. * This method is called from glyph gutter to check whether the glyph cycling * column should be drawn or not. */ public boolean isGlyphButtonColumn() { return glyphButtonColumn; } /** Creates menu item for the given action. It must handle the BaseActions, which * have localized name stored not in Action.NAME property. */ private JMenuItem createMenuItem(Action action) { if (action instanceof BaseAction) { JMenuItem item = new JMenuItem( ((BaseAction)action).getPopupMenuText(null) ); item.addActionListener(action); return item; } else { JMenuItem item = new JMenuItem( (String)action.getValue(Action.NAME) ); item.addActionListener(action); return item; } } /** Creates popup menu with all actions for the given line. */ public JPopupMenu createPopupMenu(BaseKit kit, int line) { return createMenu(kit, line, false).getPopupMenu(); } private void initMenu(JMenu pm, BaseKit kit, int line){ LineAnnotations annos = getLineAnnotations(line); Map types = new HashMap(AnnotationTypes.getTypes().getVisibleAnnotationTypeNamesCount() * 4/3); Action[] actions; boolean separator = false; boolean added = false; JMenu subMenu; if (annos != null) { // first, add actions for active annotation AnnotationDesc anno = annos.getActive(); if (anno != null) { actions = anno.getActions(); if (actions != null) { for (int i=0; i<actions.length; i++) { pm.add(createMenuItem(actions[i])); } separator = true; types.put(anno.getAnnotationType(), anno.getAnnotationType()); } } // second, add submenus for all pasive annotations AnnotationDesc[] pasiveAnnos = annos.getPasive(); added = false; if (pasiveAnnos != null) { for (int i=0; i < pasiveAnnos.length; i++) { actions = pasiveAnnos[i].getActions(); if (actions != null) { subMenu = new JMenu(pasiveAnnos[i].getAnnotationTypeInstance().getDescription()); for (int j=0; j<actions.length; j++) subMenu.add(createMenuItem(actions[j])); if (separator) { separator = false; pm.addSeparator(); } pm.add(subMenu); added = true; types.put(pasiveAnnos[i].getAnnotationType(), pasiveAnnos[i].getAnnotationType()); } } if (added) separator = true; } } // third, add all remaining possible actions to the end of the list added = false; AnnotationType type; for (Iterator i = AnnotationTypes.getTypes().getAnnotationTypeNames(); i.hasNext(); ) { type = AnnotationTypes.getTypes().getType((String)i.next()); if (type == null || !type.isVisible()) continue; if (types.get(type.getName()) != null) continue; actions = type.getActions(); if (actions != null) { subMenu = new JMenu(type.getDescription()); for (int j=0; j<actions.length; j++) subMenu.add(createMenuItem(actions[j])); if (separator) { separator = false; pm.addSeparator(); } pm.add(subMenu); added = true; } } if (added) separator = true; if (separator) pm.addSeparator(); // add checkbox for enabling/disabling of line numbers BaseAction action = (BaseAction)kit.getActionByName(BaseKit.toggleLineNumbersAction); pm.add(action.getPopupMenuItem(null)); BaseAction action2 = (BaseAction)kit.getActionByName(ExtKit.toggleToolbarAction); if (action2 != null){ pm.add(action2.getPopupMenuItem(null)); } menuInitialized = true; } private static class DelayedMenu extends JMenu{ //cgs RequestProcessor.Task task; public DelayedMenu(String s){ super(s); } public JPopupMenu getPopupMenu() { //cgs if (task!=null && (!task.isFinished())){ //cgs task.waitFinished(); //cgs } return super.getPopupMenu(); } //cgs public void addTask(RequestProcessor.Task task){ //cgs this.task = task; //cgs } } private JMenu createMenu(BaseKit kit, int line, boolean backgroundInit){ final DelayedMenu pm = new DelayedMenu(LocaleSupport.getString("generate-gutter-popup")); final BaseKit fKit = kit; final int fLine = line; if (backgroundInit){ //cgs RequestProcessor.Task task = RequestProcessor.postRequest(new Runnable(){ //cgs public void run(){ //cgs initMenu(pm, fKit, fLine); //cgs } //cgs }); //cgs pm.addTask(task); }else{ initMenu(pm, fKit, fLine); } return pm; } /** Creates popup menu with all actions for the given line. */ public JMenu createMenu(BaseKit kit, int line) { boolean bkgInit = menuInitialized; menuInitialized = true; return createMenu(kit, line, !bkgInit); } /** Manager of all annotations attached to one line. Class stores * the references to all annotations from one line in List and also * stores which annotation is active, count of visible annotations * and line number. */ static public class LineAnnotations extends Object { /** List with all annotations in this LineAnnotations */ private LinkedList annos; /** List with all visible annotations in this LineAnnotations */ private LinkedList annosVisible; /** Active annotation. Used only in case there is more than one * annotation on the line */ private AnnotationDesc active; /** Line number */ private int lineNumber; protected LineAnnotations() { annos = new LinkedList(); annosVisible = new LinkedList(); lineNumber = -1; } /** Add annotation to this line and activate it. */ public void addAnnotation(AnnotationDesc anno) { if (lineNumber == -1) lineNumber = anno.getLine(); annos.add(anno); if (anno.isVisible()) { active = anno; } refreshAnnotations(); } /** Remove annotation from this line. Refresh the active one * and count of visible. */ public void removeAnnotation(AnnotationDesc anno) { if (anno == active) activateNext(); annos.remove(anno); if (active == anno) active = null; refreshAnnotations(); } /** Return the active line annotation. */ public AnnotationDesc getActive() { return active; } /** Getter for the line number property */ public int getLine() { return lineNumber; } /** Setter for the line number property */ public void setLine(int line) { lineNumber = line; } /** Gets the array of all pasive and visible annotations */ public AnnotationDesc[] getPasive() { AnnotationDesc[] pasives = new AnnotationDesc[getCount()-1]; AnnotationDesc anno; int startIndex = annosVisible.indexOf(getActive()); int index = startIndex; int i=0; while (true) { index++; if (index >= annosVisible.size()) index = 0; if (index == startIndex) break; pasives[i] = (AnnotationDesc)annosVisible.get(index); i++; } return pasives; } /** Make the given annotation active. */ public boolean activate(AnnotationDesc anno) { int i,j; i = annosVisible.indexOf(anno); if (i == -1) { // was anno combined by some type ?? for(j=0; j < annosVisible.size(); j++) { if (annosVisible.get(j) instanceof AnnotationCombination) { if (((AnnotationCombination)annosVisible.get(j)).isAnnotationCombined(anno)) { i = j; anno = (AnnotationCombination)annosVisible.get(j); break; } } } } if (i == -1) return false; if (annosVisible.get(i) == null) return false; if (anno == active || !anno.isVisible()) return false; active = anno; return true; } /** Get count of visible annotations on the line */ public int getCount() { return annosVisible.size(); } /** Activate next annoation on the line. Used during the cycling. */ public AnnotationDesc activateNext() { if (getCount() <= 1) return active; int current = annosVisible.indexOf(active); current++; if (current >= getCount()) current = 0; active = (AnnotationDesc)annosVisible.get(current); return active; } /** Searches all combination annotation type and sort them * by getCombinationOrder into combTypes array * which is passed as paramter. */ private void fillInCombinationsAndOrderThem(LinkedList combTypes) { AnnotationType type; AnnotationType.CombinationMember[] combs; for (Iterator it = AnnotationTypes.getTypes().getAnnotationTypeNames(); it.hasNext(); ) { type = AnnotationTypes.getTypes().getType((String)it.next()); if (type == null) continue; combs = type.getCombinations(); if (combs != null && type.isWholeLine() && (combs.length >= 2 || (combs.length == 1 && combs[0].isAbsorbAll())) ) { if (type.getCombinationOrder() == 0) { combTypes.add(type); } else { boolean inserted = false; for (int i=0; i < combTypes.size(); i++) { if ( ((AnnotationType)combTypes.get(i)).getCombinationOrder() > type.getCombinationOrder()) { combTypes.add(i, type); inserted = true; break; } } if (!inserted) combTypes.add(type); } } } } /** For the given combination annotation type and list of annotations * it finds all annotations which are combined by this combination * and inserts into list of annotations new combined annotation which * wraps combined annotations. The result list of annotations can * contain null values for annotations which were combined. */ private boolean combineType(AnnotationType combType, LinkedList annosDupl) { int i, j, k; boolean matchedType; int countOfAnnos = 0; int valid_optional_count = 0; LinkedList combinedAnnos = new LinkedList(); AnnotationType.CombinationMember[] combs = combType.getCombinations(); // check that there is match between line annos & all types specified in combination boolean matchedComb = true; AnnotationType.CombinationMember comb; AnnotationDesc anno; for (i=0; i < combs.length; i++) { comb = combs[i]; matchedType = false; // check that for one specified combination type there exist some annotation for (j=0; j < annosDupl.size(); j++) { anno = (AnnotationDesc)annosDupl.get(j); if (anno == null) continue; // check whether this annotation matches the specified combination type if (comb.getName().equals( anno.getAnnotationType() )) { countOfAnnos++; // now check if the combination has specified some minimum count of annos if (comb.getMinimumCount() == 0) { matchedType = true; countOfAnnos++; combinedAnnos.add(anno); if (!comb.isAbsorbAll()) break; } else { int requiredCount = comb.getMinimumCount() - 1; for (k=j+1; (k < annosDupl.size()) && (requiredCount > 0); k++) { if (annosDupl.get(k) == null) continue; if (comb.getName().equals( ((AnnotationDesc)annosDupl.get(k)).getAnnotationType() )) { requiredCount--; } } if (requiredCount == 0) { matchedType = true; combinedAnnos.add(anno); for (k=j+1; k < annosDupl.size(); k++) { if (annosDupl.get(k) == null) continue; if (comb.getName().equals( ((AnnotationDesc)annosDupl.get(k)).getAnnotationType() )) { countOfAnnos++; combinedAnnos.add(annosDupl.get(k)); } } } break; } } } if (matchedType) { if (comb.isOptional()) valid_optional_count++; } else { if (!comb.isOptional()) { matchedComb = false; break; } } } if (combType.getMinimumOptionals() > valid_optional_count) matchedComb = false; AnnotationCombination annoComb = null; if (matchedComb) { boolean activateComb = false; for (i=0; i<combinedAnnos.size(); i++) { if (combinedAnnos.get(i) == active) activateComb = true; if (annoComb == null) { annoComb = new AnnotationCombination(combType.getName(), (AnnotationDesc)combinedAnnos.get(i)); annosDupl.set(annosDupl.indexOf(combinedAnnos.get(i)),annoComb); // replace the original annotation by the new Combined one } else { annoComb.addCombinedAnnotation((AnnotationDesc)combinedAnnos.get(i)); annosDupl.set(annosDupl.indexOf(combinedAnnos.get(i)),null); // remove annotations which were combined form the array } } if (activateComb) active = annoComb; return true; } return false; } /** Refresh the active annotation and count of visible annotations. * This method is used after change of annotation type of some annotation * on this line */ public void refreshAnnotations() { int i, j, k, count; if (!AnnotationTypes.getTypes().isCombineGlyphs().booleanValue()) { // combinations are disabled annosVisible = new LinkedList(); for (i=0; i < annos.size(); i++) { if ( ! ((AnnotationDesc)annos.get(i)).isVisible() ) continue; annosVisible.add(annos.get(i)); } } else { // combination are enabled LinkedList annosDupl = (LinkedList)annos.clone(); // List of all annotation types LinkedList combTypes = new LinkedList(); // first, fill in the array with combination types sorted by the order fillInCombinationsAndOrderThem(combTypes); for (int ct=0; ct < combTypes.size(); ct++) { combineType((AnnotationType)combTypes.get(ct), annosDupl); } annosVisible = new LinkedList(); // add remaining not combined annotations into the line annotations array for (i=0; i < annosDupl.size(); i++) { if (annosDupl.get(i) != null && ((AnnotationDesc)annosDupl.get(i)).isVisible() ) annosVisible.add(annosDupl.get(i)); } } // update the active annotation if (annosVisible.indexOf(active) == -1) { if (annosVisible.size() > 0) active = (AnnotationDesc)annosVisible.get(0); else active = null; } } /** Is this given mark still referenced by some annotation or it * can be removed from the draw mark chain */ public boolean isMarkStillReferenced(Mark mark) { AnnotationDesc anno; for( Iterator it = annos.listIterator(); it.hasNext(); ) { anno = (AnnotationDesc)it.next(); if (anno.getMark() == mark) return true; } return false; } public Iterator getAnnotations() { return annos.iterator(); } } /** Listener for listening on changes in Annotations object.*/ public interface AnnotationsListener extends EventListener { /** This method is fired when annotations on the line are changed - * annotation was added, removed, changed, etc. */ public void changedLine(int Line); /** It is not possible to trace what have changed and so the listeners * are only informed that something has changed and that the change * must be reflected somehow (most probably by complete redraw). */ public void changedAll(); } /** Annotation which is used for representation of combined annotations. * Some basic operations like getLine etc. are delegated to one of the * annotations which are representd by this combined annotation. The only * added functionality is for tooltip text and annotation type. */ private static class AnnotationCombination extends AnnotationDesc { /** Delegate annotaiton */ private AnnotationDesc delegate; /** Annotation type */ private String type; /** List of annotations which are combined */ private LinkedList list; public AnnotationCombination(String type, AnnotationDesc delegate) { super(delegate.getOffset(), delegate.getLength()); this.delegate = delegate; this.type = type; updateAnnotationType(); list = new LinkedList(); list.add(delegate); } /** Getter for offset of this annotation */ public int getOffset() { return delegate.getOffset(); } /** Getter for line number of this annotation */ public int getLine() { return delegate.getLine(); } /** Getter for localized tooltip text for this annotation */ public String getShortDescription() { return getAnnotationTypeInstance().getDescription(); } /** Getter for annotation type name */ public String getAnnotationType() { return type; } /** Add the annotation to this combination */ public void addCombinedAnnotation(AnnotationDesc anno) { list.add(anno); } /** Is the given annotation part of this combination */ public boolean isAnnotationCombined(AnnotationDesc anno) { if (list.indexOf(anno) == -1) return false; else return true; } /** Get Mark which represent this annotation in document */ Mark getMark() { return delegate.getMark(); } } }