/* * 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.openide.text; import java.awt.Color; import java.awt.Component; import java.beans.*; import javax.swing.text.*; import javax.swing.JEditorPane; import org.openide.options.SystemOption; import javax.swing.SwingUtilities; /** Dummy class holding utility methods for working with NetBeans document conventions. * * @author Jaroslav Tulach */ public final class NbDocument extends Object { private NbDocument() {} /** Attribute that signals that a given character is guarded (cannot * be modified). Implements {@link javax.swing.text.AttributeSet.CharacterAttribute} to signal that * this attribute applies to characters, not paragraphs. */ public static final Object GUARDED = new AttributeSet.CharacterAttribute () {}; /** Attribute set that adds to a part of document guarded flag */ private static final SimpleAttributeSet ATTR_ADD = new SimpleAttributeSet (); /** Attribute set to remove the guarded flag. */ private static final SimpleAttributeSet ATTR_REMOVE = new SimpleAttributeSet (); static { ATTR_ADD.addAttribute(GUARDED, Boolean.TRUE); ATTR_REMOVE.addAttribute(GUARDED, Boolean.FALSE); } /** Name of style attached to documents to mark a paragraph (line) * as a (debugger) breakpoint. */ public static final String BREAKPOINT_STYLE_NAME = "NbBreakpointStyle"; // NOI18N /** Name of style attached to documents to mark a paragraph (line) * as erroneous. */ public static final String ERROR_STYLE_NAME = "NbErrorStyle"; // NOI18N /** Name of style attached to documents to mark a paragraph (line) * as current (in a debugger). */ public static final String CURRENT_STYLE_NAME = "NbCurrentStyle"; // NOI18N /** Name of style attached to documents to unmark a paragraph (line) * as anything special. */ public static final String NORMAL_STYLE_NAME = "NbNormalStyle"; // NOI18N /** Find the root element of all lines. * All conforming NetBeans documents * should return a valid element. * * @param doc styled document (expecting NetBeans document) * @return the root element * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static Element findLineRootElement (StyledDocument doc) { checkDocParameter(doc); Element e = doc.getParagraphElement (0).getParentElement (); if (e == null) { // try default root (should work for text/plain) e = doc.getDefaultRootElement (); } return e; } /** For given document and an offset, find the line number. * @param doc the document * @param offset offset in the document * @return the line number for that offset * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static int findLineNumber (StyledDocument doc, int offset) { Element paragraphsParent = findLineRootElement (doc); return paragraphsParent.getElementIndex (offset); } /** Finds column number given an offset. * @param doc the document * @param offset offset in the document * @return column within the line of that offset (counting starts from zero) * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static int findLineColumn (StyledDocument doc, int offset) { Element paragraphsParent = findLineRootElement (doc); int indx = paragraphsParent.getElementIndex (offset); return offset - paragraphsParent.getElement (indx).getStartOffset (); } /** Finds offset of the beginning of a line. * @param doc the document * @param lineNumber number of the line to find the start of (zero-based) * @return offset * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * @exception IndexOutOfBoundsException when incorrect * <code>lineNumber</code> value is inserted */ public static int findLineOffset (StyledDocument doc, int lineNumber) { Element paragraphsParent = findLineRootElement (doc); Element line = paragraphsParent.getElement (lineNumber); if(line == null) { throw new IndexOutOfBoundsException( "Index=" + lineNumber + " is out of bounds."); // NOI18N } return line.getStartOffset (); } /** Creates position with a bias. If the bias is {@link javax.swing.text.Position.Bias#Backward} * then if an insert occures at the position, the text is inserted * after the position. If the bias is {@link javax.swing.text.Position.Bias#Forward <code>Forward</code>}, then the text is * inserted before the position. * <P> * The method checks if the document implements {@link PositionBiasable}, * and if so, {@link PositionBiasable#createPosition <code>createPosition</code>} is called. * Otherwise an attempt is made to provide a <code>Position</code> with the correct behavior. * * @param doc document to create position in * @param offset the current offset for the position * @param bias the bias to use for the position * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * @exception BadLocationException if the offset is invalid */ public static Position createPosition ( Document doc, int offset, Position.Bias bias ) throws BadLocationException { checkDocParameter(doc); if (doc instanceof PositionBiasable) { return ((PositionBiasable)doc).createPosition (offset, bias); } else { if (bias == Position.Bias.Forward) { // default behaviour return doc.createPosition (offset); } else { // use our special position return BackwardPosition.create (doc, offset); } } } /** Mark part of a document as guarded (immutable to the user). * @param doc styled document * @param offset offset to start at * @param len length of text to mark as guarded * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static void markGuarded (StyledDocument doc, int offset, int len) { checkDocParameter(doc); doc.setCharacterAttributes (offset, len, ATTR_ADD, false); } /** Remove guarded mark on a block of a document. * @param doc styled document * @param offset offset to start at * @param len length of text to mark as unguarded * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static void unmarkGuarded (StyledDocument doc, int offset, int len) { checkDocParameter(doc); doc.setCharacterAttributes (offset, len, ATTR_REMOVE, false); } /** Inserts a text into given offset and marks it guarded. * @param doc document to insert to * @param offset offset of insertion * @param txt string text to insert * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static void insertGuarded (StyledDocument doc, int offset, String txt) throws BadLocationException { checkDocParameter(doc); doc.insertString (offset, txt, ATTR_ADD); } /** Attach a breakpoint to a line in the document. * If the document has a defined style named {@link #BREAKPOINT_STYLE_NAME}, it is used. * Otherwise, a new style is defined. * * @param doc the document * @param offset identifies the line to set breakpoint to * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * * @deprecated since 1.20. Use addAnnotation() instead */ public static void markBreakpoint (StyledDocument doc, int offset) { checkDocParameter(doc); Style bp = doc.getStyle (BREAKPOINT_STYLE_NAME); if (bp == null) { // create the style bp = doc.addStyle (BREAKPOINT_STYLE_NAME, null); if (bp == null) return; bp.addAttribute ( StyleConstants.ColorConstants.Background, Color.red ); bp.addAttribute ( StyleConstants.ColorConstants.Foreground, Color.white ); } doc.setLogicalStyle (offset, bp); } /** Mark a line as erroneous (e.g. by the compiler). * If the document has a defined style named {@link #ERROR_STYLE_NAME}, it is used. * Otherwise, a new style is defined. * * @param doc the document * @param offset identifies the line to mark * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * * @deprecated since 1.20. Use addAnnotation() instead */ public static void markError (StyledDocument doc, int offset) { checkDocParameter(doc); Style bp = doc.getStyle (ERROR_STYLE_NAME); if (bp == null) { // create the style bp = doc.addStyle (ERROR_STYLE_NAME, null); if (bp == null) return; bp.addAttribute ( StyleConstants.ColorConstants.Background, Color.green ); bp.addAttribute ( StyleConstants.ColorConstants.Foreground, Color.black ); } doc.setLogicalStyle (offset, bp); } /** Marks a line as current (e.g. for the debugger). * If the document has a defined style named {@link #CURRENT_STYLE_NAME}, it is used. * Otherwise, a new style is defined. * * @param doc the document * @param offset identifies the line to mark * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * * @deprecated since 1.20. Use addAnnotation() instead */ public static void markCurrent (StyledDocument doc, int offset) { checkDocParameter(doc); Style bp = doc.getStyle (CURRENT_STYLE_NAME); if (bp == null) { // create the style bp = doc.addStyle (CURRENT_STYLE_NAME, null); if (bp == null) return; bp.addAttribute ( StyleConstants.ColorConstants.Background, Color.blue ); bp.addAttribute ( StyleConstants.ColorConstants.Foreground, Color.white ); } doc.setLogicalStyle (offset, bp); } /** * Mark a line as normal (no special attributes). * This uses the dummy style named {@link #NORMAL_STYLE_NAME}. * This method should be used to undo the effect of {@link #markBreakpoint}, {@link #markError} and {@link #markCurrent}. * @param doc the document * @param offset identified the line to unmark * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * * @deprecated since 1.20. Use addAnnotation() instead */ public static void markNormal (StyledDocument doc, int offset) { checkDocParameter(doc); Style st = doc.getStyle (NORMAL_STYLE_NAME); if (st == null) st = doc.addStyle (NORMAL_STYLE_NAME, null); if (st != null) { doc.setLogicalStyle (offset, st); } } /** Locks the document to have exclusive access to it. * Documents implementing {@link Lockable} can specify exactly how to do this. * * @param doc document to lock * @param run the action to run * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ public static void runAtomic (StyledDocument doc, Runnable run) { checkDocParameter(doc); if (doc instanceof WriteLockable) { // use the method ((WriteLockable)doc).runAtomic (run); } else { // transfer the runnable to event dispatch thread synchronized (doc) { run.run (); } } } /** Executes given runnable in "user mode" does not allowing any modifications * to parts of text marked as guarded. The actions should be run as "atomic" so * either happen all at once or none at all (if a guarded block should be modified). * * @param doc document to modify * @param run runnable to run in user mode that will have exclusive access to modify the document * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. * @exception BadLocationException if a modification of guarded text occured * and that is why no changes to the document has been done. */ public static void runAtomicAsUser (StyledDocument doc, Runnable run) throws BadLocationException { checkDocParameter(doc); if (doc instanceof WriteLockable) { // use the method ((WriteLockable)doc).runAtomicAsUser (run); } else { // transfer the runnable to event dispatch thread synchronized (doc) { run.run (); } } } /** Helper method for checking document parameter validity. * @exception NullPointerException If the <code>doc</code> parameter * is <code>null</code>. */ private static void checkDocParameter(Document doc) { if(doc == null) { throw new NullPointerException( "Invalid doc parameter. Document may not be null!"); // NOI18N } } /** Find a way to print a given document. * If the document implements the correct interface(s) then the document is returned, * else a default {@link java.awt.print.Printable} is used as a wrapper. In this last case it is useful * to implement {@link NbDocument.Printable} to describe how to print in terms of * attributed characters, rather than specifying the complete page layout from scratch. * * @param doc the document to find printing support for * @return an object that is instance of eith {@link java.awt.print.Printable} or {@link java.awt.print.Pageable} */ public static Object findPageable(StyledDocument doc) { if (doc instanceof java.awt.print.Pageable) { return doc; } else if (doc instanceof java.awt.print.Printable) { return doc; } else { return new DefaultPrintable(doc); } } /** Add annotation to the document. For annotation of whole line * the length parameter can be ignored (specify value -1). * @param doc the document which will be annotated * @param startPos position which represent begining * of the annotated text * @param length length of the annotated text. If -1 is specified * the whole line will be annotated * @param annotation annotation which is attached to this text * @since 1.20 */ public static void addAnnotation (final StyledDocument doc, final Position startPos, final int length, final Annotation annotation) { if (!(doc instanceof Annotatable)) return; if (SwingUtilities.isEventDispatchThread()) { ((Annotatable)doc).addAnnotation (startPos, length, annotation); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { ((Annotatable)doc).addAnnotation (startPos, length, annotation); } }); } } /** Removal of added annotation. * @param doc the annotated document * @param annotation annotation which is going to be removed * @since 1.20 */ public static void removeAnnotation (final StyledDocument doc, final Annotation annotation) { if (!(doc instanceof Annotatable)) return; if (SwingUtilities.isEventDispatchThread()) { ((Annotatable)doc).removeAnnotation (annotation); } else { SwingUtilities.invokeLater(new Runnable() { public void run() { ((Annotatable)doc).removeAnnotation (annotation); } }); } } /** @deprecated Not useful for anything. */ public static final Colors COLORS = new Colors (); /** @deprecated Not useful for anything. */ public static final class Colors extends SystemOption { public static final String PROP_BREAKPOINT = BREAKPOINT_STYLE_NAME; public static final String PROP_ERROR = ERROR_STYLE_NAME; public static final String PROP_CURRENT = CURRENT_STYLE_NAME; static final long serialVersionUID =-9152250591365746193L; public void setBreakpoint (Color c) {} public Color getBreakpoint () { return new Color (127, 127, 255); } public void setError (Color c) {} public Color getError () { return Color.red; } public void setCurrent (Color c) {} public Color getCurrent () { return Color.magenta; } public String displayName () { return "COLORS"; // NOI18N } } /** Specialized version of document that knows how to lock the document * for complex modifications. */ public interface WriteLockable extends Document { /** Executes given runnable in lock mode of the document. * In this mode, all redrawing is stopped and the caller has exclusive * access to all modifications to the document. * <P> * By definition there should be only one locker at a time. Sample implementation, if you are extending {@link AbstractDocument}: * * <p><code> * writeLock();<br> * try {<br> *   r.run();<br> * } finally {<br> *   writeUnlock();<br> * } * </code> * * @param r runnable to run while locked * * @see NbDocument#runAtomic */ public void runAtomic (Runnable r); /** Executes given runnable in "user mode" does not allowing any modifications * to parts of text marked as guarded. The actions should be run as "atomic" so * either happen all at once or none at all (if a guarded block should be modified). * * @param r runnable to run in user mode that will have exclusive access to modify the document * @exception BadLocationException if a modification of guarded text occured * and that is why no changes to the document has been done. */ public void runAtomicAsUser (Runnable r) throws BadLocationException; } /** Document which may support styled text printing. * Any document that wishes to support special formatting while printing * can implement this interface and provide a <code>AttributedCharacterIterator</code> * specifying colors, fonts, etc. */ public interface Printable extends Document { /** Get an attributed character iterator for the document, so that it may be printed. * <p>For a convenient way to do this, you may use {@link AttributedCharacters#iterator a simple implementation} * of an * attributed character list. * @return list of <code>AttributedCharacterIterator</code>s to be used for printing * * @see NbDocument#findPageable */ public java.text.AttributedCharacterIterator[] createPrintIterators(); } /** Enhanced version of document that provides better support for * holding and working with biased positions. It adds one new method * {@link #createPosition} that creates * a position that moves either to the left or to the right when an insertion * is performed at it. * <P> * If a document implements this interface, the new method is * used in {@link NbDocument#createPosition}. * If not, special support for the position is created. */ public interface PositionBiasable extends Document { /** Creates position with a bias. If the bias is {@link javax.swing.text.Position.Bias#Backward} * then if an insert occures at the position, the text is inserted * after the position. If the bias is {@link javax.swing.text.Position.Bias#Forward <code>Forward</code>}, then the text is * inserted before the position. * * @param offset the offset for the position * @param bias the bias to use for the position * @exception BadLocationException if the offset is invalid * * @see NbDocument#createPosition */ public Position createPosition (int offset, Position.Bias bias) throws BadLocationException; } /** Enabled documents to add special UI components to their Editor pane. * If this interface is implemented by the Editor document, it can be used * to add other components (such as toolbars) to the pane. */ public interface CustomEditor extends Document { /** Create a whole editor component over the given <code>JEditorPane</code>. * The implementation should generally add some kind of scrolling * support to the given <code>JEditorPane</code> (typically with scrollbars), * possibly some other components * according to the desired layout, * and return the resulting container. * @param j editor pane over which the resulting component * will be built * @return component encapsulating the pane and all other * custom UI components */ public Component createEditor(JEditorPane j); } /** Enhanced version of document which is capable of * attaching/detaching of annotations. It is guaranteed that * annotations are added/removed to document only in AWT thread. * @since 1.20 */ public interface Annotatable extends Document { /** Add annotation to the document. For annotation of whole line * the length parameter can be ignored (specify value -1). * @param startPos position which represent begining * of the annotated text * @param length length of the annotated text. If -1 is specified * the whole line will be annotated * @param annotation annotation which is attached to this text */ public void addAnnotation(Position startPos, int length, Annotation annotation); /** Removal of added annotation. * @param annotation annotation which is going to be removed */ public void removeAnnotation(Annotation annotation); } }