![JAR search and dependency download from the Maven repository](/logo.png)
org.netbeans.editor.Annotations Maven / Gradle / Ivy
/*
* 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 java.util.LinkedList;
import java.util.HashMap;
import org.netbeans.editor.BaseDocument;
import org.netbeans.editor.DrawLayerFactory;
import javax.swing.text.BadLocationException;
import java.util.Iterator;
import java.util.ArrayList;
import javax.swing.event.DocumentListener;
import org.netbeans.editor.BaseDocumentEvent;
import javax.swing.event.DocumentEvent;
import javax.swing.event.EventListenerList;
import java.util.EventListener;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.Comparator;
import org.netbeans.editor.AnnotationDesc;
import org.netbeans.editor.AnnotationTypes;
import javax.swing.JPopupMenu;
import javax.swing.Action;
import java.util.Map;
import java.util.TreeSet;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.SwingUtilities;
import org.netbeans.editor.ext.ExtKit;
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;
/** Sorts the subMenu items */
public static final Comparator MENU_COMPARATOR = new MenuComparator();
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) {
// Should always be run in EQ
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Must be run in EQ"); // NOI18N
}
// create mark for this annotation. One mark can be shared by more annotations
MarkChain chain = getLayer().getMarkChain();
try {
/* Always adds a fresh mark. It helps to behave well
* in the following scenario:
* 1. add annotaion at line e.g. 3.
* 2. add annotation at line 4.
* 3. goto begining of line 2.
* 4. select lines 2,3,4,5 by pressing down arrow.
* 5. remove selected lines.
* 6. Undo by ctrl-z.
* If reusing marks this will result into line selection
* being at line 2 but gutter marking of annotations
* being at line 4.
* It happens because although DocumentLine.updatePositionRef()
* removes and adds the annotations on modified lines, it does that
* sequentially for each line so it removes
* first of the two DocumentLine's annotations
* and tries to readd it to begining of removed block
* but it finds there an existing mark
* from second DocumentLine's annotation.
* It reuses the mark but as the mark was inside
* the removed block originally the undo restores its position
* inside the block.
* Creation of fresh marks fixes that problem.
*/
chain.addMark(anno.getOffset(), true);
} catch (BadLocationException e) {
/*
* This is problematic point.
* Once this place is reached the annotation desc
* will have null mark and will
* not in fact be actively present
* in the document.
* Such annotation will then throw NPE
* in removeAnnotation().
* Hopefully causes of getting to this place
* were eliminated.
*/
throw new IllegalStateException("offset=" + anno.getOffset() // NOI18N
+ ", docLen=" + doc.getLength()); // NOI18N
}
// attach created mark to annotation
MarkFactory.ChainDrawMark annoMark = chain.getAddedMark();
if (annoMark == null) {
throw new NullPointerException();
}
anno.setMark(annoMark);
// #33165 - different strategy - first trying to search
// by the mark in the map [mark, lineAnnos]. That should
// eliminate problems when a mark is tried to be removed
// from the mark chain a second time.
// Hopefully this will not cause any other sorts of problems.
LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(annoMark);
if (lineAnnos == null) {
// fine LineAnnotations instance corresponding to the line of this annotation
// or create new LineAnnotations if this is first annotation on this line
lineAnnos = getLineAnnotations(anno.getLine());
if (lineAnnos == null) {
lineAnnos = new LineAnnotations();
lineAnnos.addAnnotation(anno);
if (lineAnnotationsByMark.put(anno.getMark(), lineAnnos) != null) {
throw new IllegalStateException("Mark already in the map."); // NOI18N
}
// 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);
}
} else { // mark was already in lineAnnotationsByMark map
lineAnnos.addAnnotation(anno);
}
// 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());
// System.out.println("AFTER ADD:\n" + dumpLineAnnotationsArray());
}
/** Remove annotation */
public void removeAnnotation(AnnotationDesc anno) {
// Should always be run in EQ
if (!SwingUtilities.isEventDispatchThread()) {
throw new IllegalStateException("Must be run in EQ"); // NOI18N
}
// find LineAnnotations for the mark
MarkFactory.ChainDrawMark annoMark = (MarkFactory.ChainDrawMark)anno.getMark();
if (annoMark == null) {
throw new NullPointerException();
}
LineAnnotations lineAnnos = (LineAnnotations)lineAnnotationsByMark.get(annoMark);
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(annoMark)) {
if (lineAnnotationsByMark.remove(annoMark) != lineAnnos) {
throw new IllegalStateException();
}
MarkChain chain = getLayer().getMarkChain();
// Partial fix of #33165 - rather remove mark explicitly than through its offset
//chain.removeMark(anno.getOffset());
if (!chain.removeMark(annoMark)) {
throw new IllegalStateException("Mark not removed"); // NOI18N
}
}
// 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);
// System.out.println("AFTER REMOVE:\n" + dumpLineAnnotationsArray());
}
/** 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==null || 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;
// since fix of #33165 it is possible to use binary search here
int low = 0;
int high = lineAnnotationsArray.size() - 1;
while (low <= high) {
int mid = (low + high) / 2;
annos = (LineAnnotations)lineAnnotationsArray.get(mid);
int annosLine = annos.getLine();
if (line < annosLine) {
high = mid - 1;
} else if (line > annosLine) {
low = mid + 1;
} else {
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;
// since fix of #33165 it is possible to use binary search here
int low = 0;
int high = lineAnnotationsArray.size() - 1;
while (low <= high) {
int mid = (low + high) / 2;
annos = (LineAnnotations)lineAnnotationsArray.get(mid);
int annosLine = annos.getLine();
if (line < annosLine) {
high = mid - 1;
} else if (line > annosLine) {
low = mid + 1;
} else {
return annosLine;
}
}
// not found annotation on exact line, follow the search with simple searching
for (int i=low; i= line){
return annosLine;
}
}
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();
}
/** Return list of passive annotations attached on the line of given offset */
public AnnotationDesc[] getPassiveAnnotations(int offset){
int lineIndex = doc.getDefaultRootElement().getElementIndex(offset);
return (lineIndex>=0) ? getPasiveAnnotations(lineIndex) : null;
}
/** 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();
/* #33165 - line in line-annotations handled in a different way
LineAnnotations annos;
for (int i=0; i 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;
/* #33165 - line in line-annotations handled in a different way
int changedLine = be.getLine();
LineAnnotations annos;
LineAnnotations current = null;
for (int i=0; i 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;
}
private void addAcceleretors(Action a, JMenuItem item, BaseKit kit){
// Try to get the accelerator
javax.swing.text.JTextComponent target = Utilities.getFocusedComponent();
if (target == null) return;
javax.swing.text.Keymap km = target.getKeymap();
if (km != null) {
javax.swing.KeyStroke[] keys = km.getKeyStrokesForAction(a);
if (keys != null && keys.length > 0) {
item.setAccelerator(keys[0]);
}else{
// retrieve via actionName
String actionName = (String)a.getValue(Action.NAME);
if (actionName == null) return;
BaseAction action = (BaseAction)kit.getActionByName(actionName);
if (action == null) return;
keys = km.getKeyStrokesForAction(action);
if (keys != null && keys.length > 0) {
item.setAccelerator(keys[0]);
}
}
}
}
/** 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, BaseKit kit) {
if (action instanceof BaseAction) {
JMenuItem item = new JMenuItem( ((BaseAction)action).getPopupMenuText(null) );
item.addActionListener(action);
addAcceleretors(action, item, kit);
return item;
} else {
JMenuItem item = new JMenuItem( (String)action.getValue(Action.NAME) );
item.addActionListener(action);
addAcceleretors(action, item, kit);
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;
TreeSet orderedSubMenus = new TreeSet(MENU_COMPARATOR);
if (annos != null) {
// first, add actions for active annotation
AnnotationDesc anno = annos.getActive();
if (anno != null) {
actions = anno.getActions();
if (actions != null) {
subMenu = new JMenu(anno.getAnnotationTypeInstance().getDescription());
for (int j=0; j 0){
//pm.add(subMenu);
orderedSubMenus.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 0){
//pm.add(subMenu);
orderedSubMenus.add(subMenu);
added = true;
}
}
}
if (added)
separator = true;
/*
if (separator)
pm.addSeparator();
*/
if (!orderedSubMenus.isEmpty()){
Iterator iter = orderedSubMenus.iterator();
while(iter.hasNext()){
subMenu = (JMenu) iter.next();
pm.add(subMenu);
}
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{
RequestProcessor.Task task;
public DelayedMenu(String s){
super(s);
}
public JPopupMenu getPopupMenu() {
RequestProcessor.Task t = task;
if (t!=null && !t.isFinished()){
t.waitFinished();
}
return super.getPopupMenu();
}
void setTask(RequestProcessor.Task task){
this.task = task;
}
void clearTask() {
this.task = null; // clear the finished task to avoid leaking
}
}
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){
RequestProcessor rp = RequestProcessor.getDefault();
RequestProcessor.Task task = rp.create(new Runnable(){
public void run(){
initMenu(pm, fKit, fLine);
pm.clearTask(); // clear the finished task reference to avoid leaking
}
});
pm.setTask(task); // set before task execution so that always cleaned properly
task.schedule(0);
}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);
}
private String dumpAnnotaionDesc(AnnotationDesc ad) {
return "offset=" + ad.getOffset() // NOI18N
+ "(ls=" + doc.getParagraphElement(ad.getOffset()).getStartOffset() // NOI18N
+ "), line=" + ad.getLine() // NOI18N
+ ", type=" + ad.getAnnotationType(); // NOI18N
}
private String dumpLineAnnotationsArray() {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < lineAnnotationsArray.size(); i++) {
LineAnnotations la = (LineAnnotations)lineAnnotationsArray.get(i);
LinkedList annos = la.annos;
sb.append("[" + i + "]: line=" + la.getLine() // NOI18N
+ ", anos:"); // NOI18N
for (int j = 0; j < annos.size(); j++) {
sb.append("\n [" + j + "]: " + dumpAnnotaionDesc((AnnotationDesc)annos.get(j))); // NOI18N
}
sb.append('\n');
}
return sb.toString();
}
/** 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() {
// #33165 - delegating of getting of the line number to first anno
return (annos.size() > 0)
? ((AnnotationDesc)annos.get(0)).getLine()
: 0;
// return lineNumber;
}
/** Setter for the line number property */
public void setLine(int line) {
// lineNumber = line;
throw new IllegalStateException("Setting of line number not allowed"); // NOI18N
}
/** Gets the array of all pasive and visible annotations */
public AnnotationDesc[] getPasive() {
AnnotationDesc[] pasives = new AnnotationDesc[getCount()-1];
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 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();
}
}
public static final class MenuComparator implements Comparator {
public MenuComparator() {
}
public int compare(Object o1, Object o2) {
JMenu menuOne = (JMenu)o1;
JMenu menuTwo = (JMenu)o2;
if (menuTwo == null || menuOne == null) return 0;
String menuOneText = menuOne.getText();
String menuTwoText = menuTwo.getText();
if (menuTwoText == null || menuOneText == null) return 0;
return menuOneText.compareTo(menuTwoText);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy