0001: /*
0002: * Sun Public License Notice
0003: *
0004: * The contents of this file are subject to the Sun Public License
0005: * Version 1.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://www.sun.com/
0008: *
0009: * The Original Code is NetBeans. The Initial Developer of the Original
0010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2001 Sun
0011: * Microsystems, Inc. All Rights Reserved.
0012: */
0013:
0014: package org.netbeans.editor;
0015:
0016: import java.beans.PropertyChangeEvent;
0017: import java.beans.PropertyChangeListener;
0018: import java.util.ArrayList;
0019: import java.util.EventListener;
0020: import java.util.HashMap;
0021: import java.util.Iterator;
0022: import java.util.LinkedList;
0023: import java.util.Map;
0024:
0025: import javax.swing.Action;
0026: import javax.swing.JMenu;
0027: import javax.swing.JMenuItem;
0028: import javax.swing.JPopupMenu;
0029: import javax.swing.event.DocumentEvent;
0030: import javax.swing.event.DocumentListener;
0031: import javax.swing.event.EventListenerList;
0032: import javax.swing.text.BadLocationException;
0033:
0034: /**
0035: * Annotations class act as data model containing all annotations attached to
0036: * one document. Class uses instances of private class LineAnnotations for
0037: * grouping of added annotations by line. These objects (LineAnnotations) are
0038: * referenced from two collections. First one is Map where the key is Mark. It
0039: * is used during the drawing in DrawLayerFactory.AnnotationLayer - when the
0040: * mark appears in mark change, the LineAnnotations instance is found for it and
0041: * the active annotation on the line can be queried. Second is List where the
0042: * LineAnnotations are sorted by line number. This list is used for drawing the
0043: * annotations in the gutter when the sequential order is important.
0044: *
0045: * The class also listen on document. It need to know how many lines where
0046: * removed or added to refresh the LineAnnotations.line property.
0047: *
0048: * @author David Konecny
0049: * @since 07/2001
0050: */
0051: public class Annotations implements DocumentListener {
0052:
0053: /** Map of [Mark, LineAnnotations] */
0054: private HashMap lineAnnotationsByMark;
0055:
0056: /** List of [LineAnnotations] which is ordered by line number */
0057: private ArrayList lineAnnotationsArray;
0058:
0059: /** Drawing layer for drawing of annotations */
0060: private DrawLayerFactory.AnnotationLayer drawLayer;
0061:
0062: /** Reference to document */
0063: private BaseDocument doc;
0064:
0065: /** List of listeners on AnnotationsListener */
0066: private EventListenerList listenerList;
0067:
0068: /** Property change listener on annotation type changes */
0069: private PropertyChangeListener l;
0070:
0071: /** Property change listener on AnnotationTypes changes */
0072: private PropertyChangeListener annoTypesListener;
0073:
0074: /** Whether the column with glyph icons is visible */
0075: private boolean glyphColumn = false;
0076:
0077: /** Whether the column with cycling button is visible */
0078: private boolean glyphButtonColumn = false;
0079:
0080: public Annotations(BaseDocument doc) {
0081: lineAnnotationsByMark = new HashMap(30);
0082: lineAnnotationsArray = new ArrayList(20);
0083: listenerList = new EventListenerList();
0084:
0085: drawLayer = null;
0086: this .doc = doc;
0087:
0088: // add annotation drawing layer
0089: doc.addLayer(new DrawLayerFactory.AnnotationLayer(doc),
0090: DrawLayerFactory.ANNOTATION_LAYER_VISIBILITY);
0091:
0092: // listener on document changes
0093: this .doc.addDocumentListener(this );
0094:
0095: l = new PropertyChangeListener() {
0096: public void propertyChange(PropertyChangeEvent evt) {
0097: if (evt.getPropertyName() == AnnotationDesc.PROP_ANNOTATION_TYPE) {
0098: AnnotationDesc anno = (AnnotationDesc) evt
0099: .getSource();
0100: LineAnnotations lineAnnos = (LineAnnotations) lineAnnotationsByMark
0101: .get(anno.getMark());
0102: lineAnnos.refreshAnnotations();
0103: refreshLine(lineAnnos.getLine());
0104: }
0105: if (evt.getPropertyName() == AnnotationDesc.PROP_MOVE_TO_FRONT) {
0106: AnnotationDesc anno = (AnnotationDesc) evt
0107: .getSource();
0108: frontAnnotation(anno);
0109: }
0110: }
0111: };
0112:
0113: AnnotationTypes.getTypes().addPropertyChangeListener(
0114: annoTypesListener = new PropertyChangeListener() {
0115: public void propertyChange(PropertyChangeEvent evt) {
0116: if (evt.getPropertyName() == AnnotationTypes.PROP_COMBINE_GLYPHS) {
0117: LineAnnotations lineAnnos;
0118: for (Iterator it = lineAnnotationsArray
0119: .iterator(); it.hasNext();) {
0120: lineAnnos = (LineAnnotations) it.next();
0121: lineAnnos.refreshAnnotations();
0122: }
0123: }
0124: if (evt.getPropertyName() == AnnotationTypes.PROP_ANNOTATION_TYPES) {
0125: LineAnnotations lineAnnos;
0126: for (Iterator it = lineAnnotationsArray
0127: .iterator(); it.hasNext();) {
0128: lineAnnos = (LineAnnotations) it.next();
0129: for (Iterator it2 = lineAnnos
0130: .getAnnotations(); it2
0131: .hasNext();) {
0132: AnnotationDesc anno = (AnnotationDesc) it2
0133: .next();
0134: anno.updateAnnotationType();
0135: }
0136: }
0137: }
0138: fireChangedAll();
0139: }
0140: });
0141:
0142: }
0143:
0144: /** Finds the drawing layer for annotations */
0145: public synchronized DrawLayerFactory.AnnotationLayer getLayer() {
0146: if (drawLayer == null)
0147: drawLayer = (DrawLayerFactory.AnnotationLayer) doc
0148: .findLayer(DrawLayerFactory.ANNOTATION_LAYER_NAME);
0149: return drawLayer;
0150: }
0151:
0152: /** Add annotation */
0153: public void addAnnotation(AnnotationDesc anno) {
0154:
0155: // create mark for this annotation. One mark can be shared by more
0156: // annotations
0157: MarkChain chain = getLayer().getMarkChain();
0158: try {
0159: chain.addMark(anno.getOffset());
0160: } catch (BadLocationException e) {
0161: return;
0162: }
0163: // attach created mark to annotation
0164: anno.setMark(chain.getAddedMark());
0165:
0166: // fine LineAnnotations instance corresponding to the line of this
0167: // annotation
0168: // or create new LineAnnotations if this is first annotation on this
0169: // line
0170: LineAnnotations lineAnnos = getLineAnnotations(anno.getLine());
0171: if (lineAnnos == null) {
0172: lineAnnos = new LineAnnotations();
0173: lineAnnos.addAnnotation(anno);
0174: lineAnnotationsByMark.put(anno.getMark(), lineAnnos);
0175:
0176: // insert newly created LineAnnotations into sorted array
0177: boolean inserted = false;
0178: for (int i = 0; i < lineAnnotationsArray.size(); i++) {
0179: if (((LineAnnotations) lineAnnotationsArray.get(i))
0180: .getLine() > lineAnnos.getLine()) {
0181: lineAnnotationsArray.add(i, lineAnnos);
0182: inserted = true;
0183: break;
0184: }
0185: }
0186: if (!inserted)
0187: lineAnnotationsArray.add(lineAnnos);
0188:
0189: } else {
0190: lineAnnos.addAnnotation(anno);
0191: // check whether this mark is in lineAnnotationsByMark Map
0192: // it is possible that Line.Part annotations will have more marks
0193: // for one line
0194: if (lineAnnotationsByMark.get(anno.getMark()) == null)
0195: lineAnnotationsByMark.put(anno.getMark(), lineAnnos);
0196: }
0197:
0198: // add listener on changes of annotation type
0199: anno.addPropertyChangeListener(l);
0200:
0201: // ignore annotation types with default icon
0202: if (anno.isVisible()
0203: && (!anno.isDefaultGlyph() || (anno.isDefaultGlyph() && lineAnnos
0204: .getCount() > 1))) {
0205: glyphColumn = true;
0206: }
0207:
0208: if (lineAnnos.getCount() > 1)
0209: glyphButtonColumn = true;
0210:
0211: // notify view that it must be redrawn
0212: refreshLine(lineAnnos.getLine());
0213: }
0214:
0215: /** Remove annotation */
0216: public void removeAnnotation(AnnotationDesc anno) {
0217:
0218: // find LineAnnotations for the mark
0219: LineAnnotations lineAnnos = (LineAnnotations) lineAnnotationsByMark
0220: .get(anno.getMark());
0221: int line = lineAnnos.getLine();
0222: // remove annotation from the line
0223: lineAnnos.removeAnnotation(anno);
0224:
0225: // check if this mark is referenced or not. If not, remove it
0226: if (!lineAnnos.isMarkStillReferenced(anno.getMark())) {
0227: lineAnnotationsByMark.remove(anno.getMark());
0228: MarkChain chain = getLayer().getMarkChain();
0229: chain.removeMark(anno.getOffset());
0230: }
0231:
0232: // if there is no more annotations on the line, remove LineAnnotations
0233: if (lineAnnos.getCount() == 0) {
0234: lineAnnotationsArray.remove(lineAnnotationsArray
0235: .indexOf(lineAnnos));
0236: }
0237:
0238: // clear the mark from annotation
0239: anno.setMark(null);
0240:
0241: // remove listener on changes of annotation type
0242: anno.removePropertyChangeListener(l);
0243:
0244: // notify view that must be redrawn
0245: refreshLine(line);
0246: }
0247:
0248: /**
0249: * Finds active annotation for the Mark. It is called from DrawLayer when it
0250: * found the DrawMark
0251: */
0252: public AnnotationDesc getActiveAnnotation(Mark mark) {
0253: LineAnnotations annos;
0254: annos = (LineAnnotations) lineAnnotationsByMark.get(mark);
0255: if (annos == null) {
0256: return null;
0257: }
0258: AnnotationDesc anno = annos.getActive();
0259: // it is possible that some other mark on the line (means
0260: // some other annotations) is active
0261: if (anno.getMark() != mark) {
0262: return null;
0263: }
0264: return anno;
0265: }
0266:
0267: /** Finds LineAnnotations for the given line number */
0268: protected LineAnnotations getLineAnnotations(int line) {
0269: LineAnnotations annos;
0270: // TODO: optimize searching
0271: for (int i = 0; i < lineAnnotationsArray.size(); i++) {
0272: annos = (LineAnnotations) lineAnnotationsArray.get(i);
0273: if (annos.getLine() == line)
0274: return annos;
0275: }
0276: return null;
0277: }
0278:
0279: /**
0280: * Returns the active annotation for the given line number. It is called
0281: * from the glyph gutter
0282: */
0283: public AnnotationDesc getActiveAnnotation(int line) {
0284: LineAnnotations annos = getLineAnnotations(line);
0285: if (annos == null)
0286: return null;
0287: return annos.getActive();
0288: }
0289:
0290: /**
0291: * Move annotation in front of others. The activated annotation is moved in
0292: * front of other annotations on the same line
0293: */
0294: public void frontAnnotation(AnnotationDesc anno) {
0295: int line = anno.getLine();
0296: LineAnnotations annos = getLineAnnotations(line);
0297: if (annos == null)
0298: return;
0299: annos.activate(anno);
0300: refreshLine(line);
0301: }
0302:
0303: /**
0304: * Activate next annotation on the line. Used for cycling through the
0305: * annotations
0306: */
0307: public AnnotationDesc activateNextAnnotation(int line) {
0308: LineAnnotations annos = getLineAnnotations(line);
0309: if (annos == null)
0310: return null;
0311: AnnotationDesc aa = annos.activateNext();
0312: refreshLine(line);
0313: return aa;
0314: }
0315:
0316: /** Get next line number with some annotation */
0317: public int getNextLineWithAnnotation(int line) {
0318: LineAnnotations annos;
0319: // TODO: optimize searching
0320: for (int i = 0; i < lineAnnotationsArray.size(); i++) {
0321: annos = (LineAnnotations) lineAnnotationsArray.get(i);
0322: if (annos.getLine() >= line)
0323: return annos.getLine();
0324: }
0325: return -1;
0326: }
0327:
0328: /** Get next line number with some annotation */
0329: public AnnotationDesc getAnnotation(int line, String type) {
0330: return null;
0331: }
0332:
0333: /** Return list of pasive annotations which should be drawn on the backgorund */
0334: public AnnotationDesc[] getPasiveAnnotations(int line) {
0335: LineAnnotations annos = getLineAnnotations(line);
0336: if (annos == null)
0337: return null;
0338: if (annos.getCount() <= 1)
0339: return null;
0340: return annos.getPasive();
0341: }
0342:
0343: /** Returns number of visible annotations on the line */
0344: public int getNumberOfAnnotations(int line) {
0345: LineAnnotations annos = getLineAnnotations(line);
0346: if (annos == null)
0347: return 0;
0348: return annos.getCount();
0349: }
0350:
0351: /** Notify view that it is necessary to redraw the line of the document */
0352: protected void refreshLine(int line) {
0353: fireChangedLine(line);
0354: int start = Utilities.getRowStartFromLineOffset(doc, line);
0355: int end = Utilities.getRowStartFromLineOffset(doc, line + 1);
0356: if (end == -1)
0357: end = doc.getLength();
0358: doc.repaintBlock(start, end);
0359: }
0360:
0361: /**
0362: * Checks the number of removed lines and recalculate LineAnnotations.line
0363: * property
0364: */
0365: public void removeUpdate(DocumentEvent e) {
0366: BaseDocumentEvent be = (BaseDocumentEvent) e;
0367: int countOfDeletedLines = be.getLFCount();
0368: if (countOfDeletedLines == 0)
0369: return;
0370:
0371: int changedLine = be.getLine();
0372:
0373: LineAnnotations annos;
0374: for (int i = 0; i < lineAnnotationsArray.size(); i++) {
0375: annos = (LineAnnotations) lineAnnotationsArray.get(i);
0376: if (annos.getLine() > changedLine
0377: && annos.getLine() < changedLine
0378: + countOfDeletedLines)
0379: annos.setLine(changedLine);
0380: if (annos.getLine() > changedLine)
0381: annos.setLine(annos.getLine() - countOfDeletedLines);
0382: }
0383: // fire event to AnnotationsListeners that everything should be redraw
0384: fireChangedAll();
0385: }
0386:
0387: /**
0388: * Checks the number of inserted lines and recalculate LineAnnotations.line
0389: * property
0390: */
0391: public void insertUpdate(DocumentEvent e) {
0392: BaseDocumentEvent be = (BaseDocumentEvent) e;
0393: int countOfInsertedLines = be.getLFCount();
0394: if (countOfInsertedLines == 0)
0395: return;
0396:
0397: int changedLine = be.getLine();
0398:
0399: LineAnnotations annos;
0400: LineAnnotations current = null;
0401: for (int i = 0; i < lineAnnotationsArray.size(); i++) {
0402: annos = (LineAnnotations) lineAnnotationsArray.get(i);
0403: if (annos.getLine() == changedLine
0404: && annos.getActive().getOffset() > e.getOffset())
0405: current = annos;
0406: if (annos.getLine() > changedLine)
0407: annos.setLine(annos.getLine() + countOfInsertedLines);
0408: }
0409: if (current != null)
0410: current.setLine(current.getLine() + countOfInsertedLines);
0411:
0412: // fire event to AnnotationsListeners that everything should be redraw
0413: fireChangedAll();
0414: }
0415:
0416: /** Gives notification that an attribute or set of attributes changed. */
0417: public void changedUpdate(DocumentEvent e) {
0418: }
0419:
0420: /** Add AnnotationsListener listener */
0421: public void addAnnotationsListener(AnnotationsListener listener) {
0422: listenerList.add(AnnotationsListener.class, listener);
0423: }
0424:
0425: /** Remove AnnotationsListener listener */
0426: public void removeAnnotationsListener(AnnotationsListener listener) {
0427: listenerList.remove(AnnotationsListener.class, listener);
0428: }
0429:
0430: /** Fire AnnotationsListener.ChangedLine change */
0431: protected void fireChangedLine(int line) {
0432: // Guaranteed to return a non-null array
0433: Object[] listeners = listenerList.getListenerList();
0434: // Process the listeners last to first, notifying
0435: // those that are interested in this event
0436: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0437: if (listeners[i] == AnnotationsListener.class) {
0438: // Lazily create the event:
0439: // if (e == null)
0440: // e = new ListSelectionEvent(this, firstIndex, lastIndex);
0441: ((AnnotationsListener) listeners[i + 1])
0442: .changedLine(line);
0443: }
0444: }
0445: }
0446:
0447: /** Fire AnnotationsListener.ChangedAll change */
0448: protected void fireChangedAll() {
0449: // Guaranteed to return a non-null array
0450: Object[] listeners = listenerList.getListenerList();
0451: // Process the listeners last to first, notifying
0452: // those that are interested in this event
0453: for (int i = listeners.length - 2; i >= 0; i -= 2) {
0454: if (listeners[i] == AnnotationsListener.class) {
0455: // Lazily create the event:
0456: // if (e == null)
0457: // e = new ListSelectionEvent(this, firstIndex, lastIndex);
0458: ((AnnotationsListener) listeners[i + 1]).changedAll();
0459: }
0460: }
0461: }
0462:
0463: /**
0464: * Return whether this document has or had any glyph icon attached. This
0465: * method is called from glyph gutter to check whether the glyph column
0466: * should be drawn or not.
0467: */
0468: public boolean isGlyphColumn() {
0469: return glyphColumn;
0470: }
0471:
0472: /**
0473: * Return whether this document has or had more annotations on one line.
0474: * This method is called from glyph gutter to check whether the glyph
0475: * cycling column should be drawn or not.
0476: */
0477: public boolean isGlyphButtonColumn() {
0478: return glyphButtonColumn;
0479: }
0480:
0481: /**
0482: * Creates menu item for the given action. It must handle the BaseActions,
0483: * which have localized name stored not in Action.NAME property.
0484: */
0485: private JMenuItem createMenuItem(Action action) {
0486: if (action instanceof BaseAction) {
0487: JMenuItem item = new JMenuItem(((BaseAction) action)
0488: .getPopupMenuText(null));
0489: item.addActionListener(action);
0490: return item;
0491: } else {
0492: JMenuItem item = new JMenuItem((String) action
0493: .getValue(Action.NAME));
0494: item.addActionListener(action);
0495: return item;
0496: }
0497: }
0498:
0499: /** Creates popup menu with all actions for the given line. */
0500: public JPopupMenu createPopupMenu(BaseKit kit, int line) {
0501: return createMenu(kit, line).getPopupMenu();
0502: }
0503:
0504: /** Creates popup menu with all actions for the given line. */
0505: public JMenu createMenu(BaseKit kit, int line) {
0506: LineAnnotations annos = getLineAnnotations(line);
0507: Map types = new HashMap(AnnotationTypes.getTypes()
0508: .getVisibleAnnotationTypeNamesCount() * 4 / 3);
0509:
0510: JMenu pm = new JMenu(LocaleSupport
0511: .getString("generate-gutter-popup"));
0512: Action[] actions;
0513: boolean separator = false;
0514: boolean added = false;
0515: JMenu subMenu;
0516:
0517: if (annos != null) {
0518:
0519: // first, add actions for active annotation
0520: AnnotationDesc anno = annos.getActive();
0521: if (anno != null) {
0522: actions = anno.getActions();
0523: if (actions != null) {
0524: for (int i = 0; i < actions.length; i++) {
0525: pm.add(createMenuItem(actions[i]));
0526: }
0527: separator = true;
0528: types.put(anno.getAnnotationType(), anno
0529: .getAnnotationType());
0530: }
0531: }
0532:
0533: // second, add submenus for all pasive annotations
0534: AnnotationDesc[] pasiveAnnos = annos.getPasive();
0535: added = false;
0536: if (pasiveAnnos != null) {
0537: for (int i = 0; i < pasiveAnnos.length; i++) {
0538: actions = pasiveAnnos[i].getActions();
0539: if (actions != null) {
0540: subMenu = new JMenu(pasiveAnnos[i]
0541: .getAnnotationTypeInstance()
0542: .getDescription());
0543: for (int j = 0; j < actions.length; j++)
0544: subMenu.add(createMenuItem(actions[j]));
0545: if (separator) {
0546: separator = false;
0547: pm.addSeparator();
0548: }
0549: pm.add(subMenu);
0550: added = true;
0551: types.put(pasiveAnnos[i].getAnnotationType(),
0552: pasiveAnnos[i].getAnnotationType());
0553: }
0554: }
0555: if (added)
0556: separator = true;
0557: }
0558: }
0559:
0560: // third, add all remaining possible actions to the end of the list
0561: added = false;
0562: AnnotationType type;
0563: for (Iterator i = AnnotationTypes.getTypes()
0564: .getAnnotationTypeNames(); i.hasNext();) {
0565: type = AnnotationTypes.getTypes()
0566: .getType((String) i.next());
0567: if (type == null || !type.isVisible())
0568: continue;
0569: if (types.get(type.getName()) != null)
0570: continue;
0571: actions = type.getActions();
0572: if (actions != null) {
0573: subMenu = new JMenu(type.getDescription());
0574: for (int j = 0; j < actions.length; j++)
0575: subMenu.add(createMenuItem(actions[j]));
0576: if (separator) {
0577: separator = false;
0578: pm.addSeparator();
0579: }
0580: pm.add(subMenu);
0581: added = true;
0582: }
0583: }
0584: if (added)
0585: separator = true;
0586:
0587: if (separator)
0588: pm.addSeparator();
0589:
0590: // add checkbox for enabling/disabling of line numbers
0591: BaseAction action = (BaseAction) kit
0592: .getActionByName(BaseKit.toggleLineNumbersAction);
0593: pm.add(action.getPopupMenuItem(null));
0594:
0595: return pm;
0596: }
0597:
0598: /**
0599: * Manager of all annotations attached to one line. Class stores the
0600: * references to all annotations from one line in List and also stores which
0601: * annotation is active, count of visible annotations and line number.
0602: */
0603: static public class LineAnnotations extends Object {
0604:
0605: /** List with all annotations in this LineAnnotations */
0606: private LinkedList annos;
0607:
0608: /** List with all visible annotations in this LineAnnotations */
0609: private LinkedList annosVisible;
0610:
0611: /**
0612: * Active annotation. Used only in case there is more than one
0613: * annotation on the line
0614: */
0615: private AnnotationDesc active;
0616:
0617: /** Line number */
0618: private int lineNumber;
0619:
0620: protected LineAnnotations() {
0621: annos = new LinkedList();
0622: annosVisible = new LinkedList();
0623: lineNumber = -1;
0624: }
0625:
0626: /** Add annotation to this line and activate it. */
0627: public void addAnnotation(AnnotationDesc anno) {
0628: if (lineNumber == -1)
0629: lineNumber = anno.getLine();
0630: annos.add(anno);
0631: if (anno.isVisible()) {
0632: active = anno;
0633: }
0634: refreshAnnotations();
0635: }
0636:
0637: /**
0638: * Remove annotation from this line. Refresh the active one and count of
0639: * visible.
0640: */
0641: public void removeAnnotation(AnnotationDesc anno) {
0642: if (anno == active)
0643: activateNext();
0644: annos.remove(anno);
0645: if (active == anno)
0646: active = null;
0647: refreshAnnotations();
0648: }
0649:
0650: /** Return the active line annotation. */
0651: public AnnotationDesc getActive() {
0652: return active;
0653: }
0654:
0655: /** Getter for the line number property */
0656: public int getLine() {
0657: return lineNumber;
0658: }
0659:
0660: /** Setter for the line number property */
0661: public void setLine(int line) {
0662: lineNumber = line;
0663: }
0664:
0665: /** Gets the array of all pasive and visible annotations */
0666: public AnnotationDesc[] getPasive() {
0667: AnnotationDesc[] pasives = new AnnotationDesc[getCount() - 1];
0668: AnnotationDesc anno;
0669: int startIndex = annosVisible.indexOf(getActive());
0670: int index = startIndex;
0671: int i = 0;
0672: while (true) {
0673: index++;
0674: if (index >= annosVisible.size())
0675: index = 0;
0676: if (index == startIndex)
0677: break;
0678:
0679: pasives[i] = (AnnotationDesc) annosVisible.get(index);
0680: i++;
0681: }
0682: return pasives;
0683: }
0684:
0685: /** Make the given annotation active. */
0686: public boolean activate(AnnotationDesc anno) {
0687:
0688: int i, j;
0689: i = annosVisible.indexOf(anno);
0690:
0691: if (i == -1) {
0692: // was anno combined by some type ??
0693: for (j = 0; j < annosVisible.size(); j++) {
0694: if (annosVisible.get(j) instanceof AnnotationCombination) {
0695: if (((AnnotationCombination) annosVisible
0696: .get(j)).isAnnotationCombined(anno)) {
0697: i = j;
0698: anno = (AnnotationCombination) annosVisible
0699: .get(j);
0700: break;
0701: }
0702: }
0703: }
0704: }
0705:
0706: if (i == -1)
0707: return false;
0708:
0709: if (annosVisible.get(i) == null)
0710: return false;
0711:
0712: if (anno == active || !anno.isVisible())
0713: return false;
0714:
0715: active = anno;
0716:
0717: return true;
0718: }
0719:
0720: /** Get count of visible annotations on the line */
0721: public int getCount() {
0722: return annosVisible.size();
0723: }
0724:
0725: /** Activate next annoation on the line. Used during the cycling. */
0726: public AnnotationDesc activateNext() {
0727: if (getCount() <= 1)
0728: return active;
0729:
0730: int current = annosVisible.indexOf(active);
0731: current++;
0732: if (current >= getCount())
0733: current = 0;
0734: active = (AnnotationDesc) annosVisible.get(current);
0735: return active;
0736: }
0737:
0738: /**
0739: * Searches all combination annotation type and sort them by
0740: * getCombinationOrder into combTypes array which is passed as paramter.
0741: */
0742: private void fillInCombinationsAndOrderThem(LinkedList combTypes) {
0743: AnnotationType type;
0744: AnnotationType.CombinationMember[] combs;
0745:
0746: for (Iterator it = AnnotationTypes.getTypes()
0747: .getAnnotationTypeNames(); it.hasNext();) {
0748: type = AnnotationTypes.getTypes().getType(
0749: (String) it.next());
0750: if (type == null)
0751: continue;
0752: combs = type.getCombinations();
0753: if (combs != null
0754: && type.isWholeLine()
0755: && (combs.length >= 2 || (combs.length == 1 && combs[0]
0756: .isAbsorbAll()))) {
0757: if (type.getCombinationOrder() == 0) {
0758: combTypes.add(type);
0759: } else {
0760: boolean inserted = false;
0761: for (int i = 0; i < combTypes.size(); i++) {
0762: if (((AnnotationType) combTypes.get(i))
0763: .getCombinationOrder() > type
0764: .getCombinationOrder()) {
0765: combTypes.add(i, type);
0766: inserted = true;
0767: break;
0768: }
0769: }
0770: if (!inserted)
0771: combTypes.add(type);
0772: }
0773: }
0774: }
0775: }
0776:
0777: /**
0778: * For the given combination annotation type and list of annotations it
0779: * finds all annotations which are combined by this combination and
0780: * inserts into list of annotations new combined annotation which wraps
0781: * combined annotations. The result list of annotations can contain null
0782: * values for annotations which were combined.
0783: */
0784: private boolean combineType(AnnotationType combType,
0785: LinkedList annosDupl) {
0786:
0787: int i, j, k;
0788: boolean matchedType;
0789: int countOfAnnos = 0;
0790: int valid_optional_count = 0;
0791:
0792: LinkedList combinedAnnos = new LinkedList();
0793:
0794: AnnotationType.CombinationMember[] combs = combType
0795: .getCombinations();
0796:
0797: // check that there is match between line annos & all types
0798: // specified in combination
0799: boolean matchedComb = true;
0800: AnnotationType.CombinationMember comb;
0801: AnnotationDesc anno;
0802: for (i = 0; i < combs.length; i++) {
0803:
0804: comb = combs[i];
0805: matchedType = false;
0806:
0807: // check that for one specified combination type there exist
0808: // some annotation
0809: for (j = 0; j < annosDupl.size(); j++) {
0810:
0811: anno = (AnnotationDesc) annosDupl.get(j);
0812:
0813: if (anno == null)
0814: continue;
0815:
0816: // check whether this annotation matches the specified
0817: // combination type
0818: if (comb.getName().equals(anno.getAnnotationType())) {
0819: countOfAnnos++;
0820:
0821: // now check if the combination has specified some
0822: // minimum count of annos
0823: if (comb.getMinimumCount() == 0) {
0824: matchedType = true;
0825: countOfAnnos++;
0826: combinedAnnos.add(anno);
0827: if (!comb.isAbsorbAll())
0828: break;
0829: } else {
0830: int requiredCount = comb.getMinimumCount() - 1;
0831: for (k = j + 1; (k < annosDupl.size())
0832: && (requiredCount > 0); k++) {
0833: if (annosDupl.get(k) == null)
0834: continue;
0835: if (comb.getName().equals(
0836: ((AnnotationDesc) annosDupl
0837: .get(k))
0838: .getAnnotationType())) {
0839: requiredCount--;
0840: }
0841: }
0842: if (requiredCount == 0) {
0843: matchedType = true;
0844:
0845: combinedAnnos.add(anno);
0846: for (k = j + 1; k < annosDupl.size(); k++) {
0847: if (annosDupl.get(k) == null)
0848: continue;
0849: if (comb
0850: .getName()
0851: .equals(
0852: ((AnnotationDesc) annosDupl
0853: .get(k))
0854: .getAnnotationType())) {
0855: countOfAnnos++;
0856: combinedAnnos.add(annosDupl
0857: .get(k));
0858: }
0859: }
0860: }
0861: break;
0862: }
0863:
0864: }
0865:
0866: }
0867:
0868: if (matchedType) {
0869: if (comb.isOptional())
0870: valid_optional_count++;
0871: } else {
0872: if (!comb.isOptional()) {
0873: matchedComb = false;
0874: break;
0875: }
0876: }
0877:
0878: }
0879: if (combType.getMinimumOptionals() > valid_optional_count)
0880: matchedComb = false;
0881:
0882: AnnotationCombination annoComb = null;
0883: if (matchedComb) {
0884:
0885: boolean activateComb = false;
0886:
0887: for (i = 0; i < combinedAnnos.size(); i++) {
0888: if (combinedAnnos.get(i) == active)
0889: activateComb = true;
0890:
0891: if (annoComb == null) {
0892: annoComb = new AnnotationCombination(combType
0893: .getName(),
0894: (AnnotationDesc) combinedAnnos.get(i));
0895: annosDupl.set(annosDupl.indexOf(combinedAnnos
0896: .get(i)), annoComb); // replace
0897: // the
0898: // original
0899: // annotation
0900: // by
0901: // the
0902: // new
0903: // Combined
0904: // one
0905: } else {
0906: annoComb
0907: .addCombinedAnnotation((AnnotationDesc) combinedAnnos
0908: .get(i));
0909: annosDupl.set(annosDupl.indexOf(combinedAnnos
0910: .get(i)), null); // remove
0911: // annotations
0912: // which
0913: // were
0914: // combined
0915: // form
0916: // the
0917: // array
0918: }
0919: }
0920: if (activateComb)
0921: active = annoComb;
0922:
0923: return true;
0924: }
0925:
0926: return false;
0927: }
0928:
0929: /**
0930: * Refresh the active annotation and count of visible annotations. This
0931: * method is used after change of annotation type of some annotation on
0932: * this line
0933: */
0934: public void refreshAnnotations() {
0935: int i, j, k, count;
0936:
0937: if (!AnnotationTypes.getTypes().isCombineGlyphs()
0938: .booleanValue()) {
0939:
0940: // combinations are disabled
0941: annosVisible = new LinkedList();
0942: for (i = 0; i < annos.size(); i++) {
0943: if (!((AnnotationDesc) annos.get(i)).isVisible())
0944: continue;
0945: annosVisible.add(annos.get(i));
0946: }
0947:
0948: } else {
0949:
0950: // combination are enabled
0951: LinkedList annosDupl = (LinkedList) annos.clone();
0952:
0953: // List of all annotation types
0954: LinkedList combTypes = new LinkedList();
0955:
0956: // first, fill in the array with combination types sorted by the
0957: // order
0958: fillInCombinationsAndOrderThem(combTypes);
0959:
0960: for (int ct = 0; ct < combTypes.size(); ct++) {
0961: combineType((AnnotationType) combTypes.get(ct),
0962: annosDupl);
0963: }
0964:
0965: annosVisible = new LinkedList();
0966:
0967: // add remaining not combined annotations into the line
0968: // annotations array
0969: for (i = 0; i < annosDupl.size(); i++) {
0970: if (annosDupl.get(i) != null
0971: && ((AnnotationDesc) annosDupl.get(i))
0972: .isVisible())
0973: annosVisible.add(annosDupl.get(i));
0974: }
0975: }
0976:
0977: // update the active annotation
0978: if (annosVisible.indexOf(active) == -1) {
0979: if (annosVisible.size() > 0)
0980: active = (AnnotationDesc) annosVisible.get(0);
0981: else
0982: active = null;
0983: }
0984: }
0985:
0986: /**
0987: * Is this given mark still referenced by some annotation or it can be
0988: * removed from the draw mark chain
0989: */
0990: public boolean isMarkStillReferenced(Mark mark) {
0991: AnnotationDesc anno;
0992: for (Iterator it = annos.listIterator(); it.hasNext();) {
0993: anno = (AnnotationDesc) it.next();
0994: if (anno.getMark() == mark)
0995: return true;
0996: }
0997: return false;
0998: }
0999:
1000: public Iterator getAnnotations() {
1001: return annos.iterator();
1002: }
1003:
1004: }
1005:
1006: /** Listener for listening on changes in Annotations object. */
1007: public interface AnnotationsListener extends EventListener {
1008:
1009: /**
1010: * This method is fired when annotations on the line are changed -
1011: * annotation was added, removed, changed, etc.
1012: */
1013: public void changedLine(int Line);
1014:
1015: /**
1016: * It is not possible to trace what have changed and so the listeners
1017: * are only informed that something has changed and that the change must
1018: * be reflected somehow (most probably by complete redraw).
1019: */
1020: public void changedAll();
1021:
1022: }
1023:
1024: /**
1025: * Annotation which is used for representation of combined annotations. Some
1026: * basic operations like getLine etc. are delegated to one of the
1027: * annotations which are representd by this combined annotation. The only
1028: * added functionality is for tooltip text and annotation type.
1029: */
1030: private static class AnnotationCombination extends AnnotationDesc {
1031:
1032: /** Delegate annotaiton */
1033: private AnnotationDesc delegate;
1034:
1035: /** Annotation type */
1036: private String type;
1037:
1038: /** List of annotations which are combined */
1039: private LinkedList list;
1040:
1041: public AnnotationCombination(String type,
1042: AnnotationDesc delegate) {
1043: super (delegate.getOffset(), delegate.getLength());
1044: this .delegate = delegate;
1045: this .type = type;
1046: updateAnnotationType();
1047: list = new LinkedList();
1048: list.add(delegate);
1049: }
1050:
1051: /** Getter for offset of this annotation */
1052: public int getOffset() {
1053: return delegate.getOffset();
1054: }
1055:
1056: /** Getter for line number of this annotation */
1057: public int getLine() {
1058: return delegate.getLine();
1059: }
1060:
1061: /** Getter for localized tooltip text for this annotation */
1062: public String getShortDescription() {
1063: return getAnnotationTypeInstance().getDescription();
1064: }
1065:
1066: /** Getter for annotation type name */
1067: public String getAnnotationType() {
1068: return type;
1069: }
1070:
1071: /** Add the annotation to this combination */
1072: public void addCombinedAnnotation(AnnotationDesc anno) {
1073: list.add(anno);
1074: }
1075:
1076: /** Is the given annotation part of this combination */
1077: public boolean isAnnotationCombined(AnnotationDesc anno) {
1078: if (list.indexOf(anno) == -1)
1079: return false;
1080: else
1081: return true;
1082: }
1083:
1084: /** Get Mark which represent this annotation in document */
1085: Mark getMark() {
1086: return delegate.getMark();
1087: }
1088:
1089: }
1090: }
|