2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
6
* The contents of this file are subject to the terms of either the GNU
7
* General Public License Version 2 only ("GPL") or the Common
8
* Development and Distribution License("CDDL") (collectively, the
9
* "License"). You may not use this file except in compliance with the
10
* License. You can obtain a copy of the License at
11
* http://www.netbeans.org/cddl-gplv2.html
12
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
13
* specific language governing permissions and limitations under the
14
* License. When distributing the software, include this License Header
15
* Notice in each file and include the License file at
16
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
17
* particular file as subject to the "Classpath" exception as provided
18
* by Sun in the GPL Version 2 section of the License file that
19
* accompanied this code. If applicable, add the following below the
20
* License Header, with the fields enclosed by brackets [] replaced by
21
* your own identifying information:
22
* "Portions Copyrighted [year] [name of copyright owner]"
26
* The Original Software is NetBeans. The Initial Developer of the Original
27
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
28
* Microsystems, Inc. All Rights Reserved.
30
* If you wish your version of this file to be governed by only the CDDL
31
* or only the GPL Version 2, indicate your decision by adding
32
* "[Contributor] elects to include this software in this distribution
33
* under the [CDDL or GPL Version 2] license." If you do not indicate a
34
* single choice of license, a recipient has the option to distribute
35
* your version of this file under either the CDDL, the GPL Version 2 or
36
* to extend the choice of license to its licensees as provided above.
37
* However, if you add GPL Version 2 code and therefore, elected the GPL
38
* Version 2 license, then the option applies only if the new code is
39
* made subject to such option by the copyright holder.
41
package org.openide.text;
43
import java.awt.Color;
44
import java.awt.Component;
48
import javax.swing.JEditorPane;
49
import javax.swing.JToolBar;
50
import javax.swing.SwingUtilities;
51
import javax.swing.text.*;
54
/** Dummy class holding utility methods for working with NetBeans document conventions.
56
* @author Jaroslav Tulach
58
public final class NbDocument extends Object {
59
/** Attribute that signals that a given character is guarded (cannot
60
* be modified). Implements {@link javax.swing.text.AttributeSet.CharacterAttribute} to signal that
61
* this attribute applies to characters, not paragraphs.
63
public static final Object GUARDED = new AttributeSet.CharacterAttribute() {
66
/** Attribute set that adds to a part of document guarded flag
68
private static final SimpleAttributeSet ATTR_ADD = new SimpleAttributeSet();
70
/** Attribute set to remove the guarded flag.
72
private static final SimpleAttributeSet ATTR_REMOVE = new SimpleAttributeSet();
75
ATTR_ADD.addAttribute(GUARDED, Boolean.TRUE);
76
ATTR_REMOVE.addAttribute(GUARDED, Boolean.FALSE);
79
/** Name of style attached to documents to mark a paragraph (line)
80
* as a (debugger) breakpoint.
82
public static final String BREAKPOINT_STYLE_NAME = "NbBreakpointStyle"; // NOI18N
84
/** Name of style attached to documents to mark a paragraph (line)
87
public static final String ERROR_STYLE_NAME = "NbErrorStyle"; // NOI18N
89
/** Name of style attached to documents to mark a paragraph (line)
90
* as current (in a debugger).
92
public static final String CURRENT_STYLE_NAME = "NbCurrentStyle"; // NOI18N
94
/** Name of style attached to documents to unmark a paragraph (line)
95
* as anything special.
97
public static final String NORMAL_STYLE_NAME = "NbNormalStyle"; // NOI18N
99
private NbDocument() {
102
/** Find the root element of all lines.
103
* All conforming NetBeans documents
104
* should return a valid element.
106
* @param doc styled document (expecting NetBeans document)
107
* @return the root element
108
* @exception NullPointerException If the <code>doc</code> parameter
109
* is <code>null</code>.
111
public static Element findLineRootElement(StyledDocument doc) {
112
checkDocParameter(doc);
114
Element e = doc.getParagraphElement(0).getParentElement();
117
// try default root (should work for text/plain)
118
e = doc.getDefaultRootElement();
124
/** For given document and an offset, find the line number.
125
* @param doc the document
126
* @param offset offset in the document
127
* @return the line number for that offset
128
* @exception NullPointerException If the <code>doc</code> parameter
129
* is <code>null</code>.
131
public static int findLineNumber(StyledDocument doc, int offset) {
133
Element paragraphsParent = findLineRootElement (doc);
134
return paragraphsParent.getElementIndex (offset);
136
return new DocumentRenderer(DocumentRenderer.FIND_LINE_NUMBER, doc, offset).renderToInt();
139
/** Finds column number given an offset.
140
* @param doc the document
141
* @param offset offset in the document
142
* @return column within the line of that offset (counting starts from zero)
143
* @exception NullPointerException If the <code>doc</code> parameter
144
* is <code>null</code>.
146
public static int findLineColumn(StyledDocument doc, int offset) {
148
Element paragraphsParent = findLineRootElement (doc);
149
int indx = paragraphsParent.getElementIndex (offset);
150
return offset - paragraphsParent.getElement (indx).getStartOffset ();
152
return new DocumentRenderer(DocumentRenderer.FIND_LINE_COLUMN, doc, offset).renderToInt();
155
/** Finds offset of the beginning of a line.
156
* @param doc the document
157
* @param lineNumber number of the line to find the start of (zero-based)
159
* @exception NullPointerException If the <code>doc</code> parameter
160
* is <code>null</code>.
161
* @exception IndexOutOfBoundsException when incorrect
162
* <code>lineNumber</code> value is inserted
164
public static int findLineOffset(StyledDocument doc, int lineNumber) {
166
Element paragraphsParent = findLineRootElement (doc);
167
Element line = paragraphsParent.getElement (lineNumber);
170
throw new IndexOutOfBoundsException(
171
"Index=" + lineNumber + " is out of bounds."); // NOI18N
174
return line.getStartOffset ();
176
return new DocumentRenderer(DocumentRenderer.FIND_LINE_OFFSET, doc, lineNumber).renderToInt();
179
/** Creates position with a bias. If the bias is {@link javax.swing.text.Position.Bias#Backward}
180
* then if an insert occures at the position, the text is inserted
181
* after the position. If the bias is {@link javax.swing.text.Position.Bias#Forward <code>Forward</code>}, then the text is
182
* inserted before the position.
184
* The method checks if the document implements {@link PositionBiasable},
185
* and if so, {@link PositionBiasable#createPosition <code>createPosition</code>} is called.
186
* Otherwise an attempt is made to provide a <code>Position</code> with the correct behavior.
188
* @param doc document to create position in
189
* @param offset the current offset for the position
190
* @param bias the bias to use for the position
191
* @exception NullPointerException If the <code>doc</code> parameter
192
* is <code>null</code>.
193
* @exception BadLocationException if the offset is invalid
195
public static Position createPosition(Document doc, int offset, Position.Bias bias)
196
throws BadLocationException {
197
checkDocParameter(doc);
199
if (doc instanceof PositionBiasable) {
200
return ((PositionBiasable) doc).createPosition(offset, bias);
202
if (bias == Position.Bias.Forward) {
204
return doc.createPosition(offset);
206
// use our special position
207
return BackwardPosition.create(doc, offset);
212
/** Mark part of a document as guarded (immutable to the user).
213
* @param doc styled document
214
* @param offset offset to start at
215
* @param len length of text to mark as guarded
216
* @exception NullPointerException If the <code>doc</code> parameter
217
* is <code>null</code>.
219
public static void markGuarded(StyledDocument doc, int offset, int len) {
220
checkDocParameter(doc);
221
doc.setCharacterAttributes(offset, len, ATTR_ADD, false);
224
/** Remove guarded mark on a block of a document.
225
* @param doc styled document
226
* @param offset offset to start at
227
* @param len length of text to mark as unguarded
228
* @exception NullPointerException If the <code>doc</code> parameter
229
* is <code>null</code>.
231
public static void unmarkGuarded(StyledDocument doc, int offset, int len) {
232
checkDocParameter(doc);
233
doc.setCharacterAttributes(offset, len, ATTR_REMOVE, false);
236
/** Inserts a text into given offset and marks it guarded.
237
* @param doc document to insert to
238
* @param offset offset of insertion
239
* @param txt string text to insert
240
* @exception NullPointerException If the <code>doc</code> parameter
241
* is <code>null</code>.
243
public static void insertGuarded(StyledDocument doc, int offset, String txt)
244
throws BadLocationException {
245
checkDocParameter(doc);
246
doc.insertString(offset, txt, ATTR_ADD);
249
/** Attach a breakpoint to a line in the document.
250
* If the document has a defined style named {@link #BREAKPOINT_STYLE_NAME}, it is used.
251
* Otherwise, a new style is defined.
253
* @param doc the document
254
* @param offset identifies the line to set breakpoint to
255
* @exception NullPointerException If the <code>doc</code> parameter
256
* is <code>null</code>.
258
* @deprecated since 1.20. Use addAnnotation() instead
261
public static void markBreakpoint(StyledDocument doc, int offset) {
262
checkDocParameter(doc);
264
Style bp = doc.getStyle(BREAKPOINT_STYLE_NAME);
268
bp = doc.addStyle(BREAKPOINT_STYLE_NAME, null);
274
bp.addAttribute(StyleConstants.ColorConstants.Background, Color.red);
275
bp.addAttribute(StyleConstants.ColorConstants.Foreground, Color.white);
278
doc.setLogicalStyle(offset, bp);
281
/** Mark a line as erroneous (e.g. by the compiler).
282
* If the document has a defined style named {@link #ERROR_STYLE_NAME}, it is used.
283
* Otherwise, a new style is defined.
285
* @param doc the document
286
* @param offset identifies the line to mark
287
* @exception NullPointerException If the <code>doc</code> parameter
288
* is <code>null</code>.
290
* @deprecated since 1.20. Use addAnnotation() instead
293
public static void markError(StyledDocument doc, int offset) {
294
checkDocParameter(doc);
296
Style bp = doc.getStyle(ERROR_STYLE_NAME);
300
bp = doc.addStyle(ERROR_STYLE_NAME, null);
306
bp.addAttribute(StyleConstants.ColorConstants.Background, Color.green);
307
bp.addAttribute(StyleConstants.ColorConstants.Foreground, Color.black);
310
doc.setLogicalStyle(offset, bp);
313
/** Marks a line as current (e.g. for the debugger).
314
* If the document has a defined style named {@link #CURRENT_STYLE_NAME}, it is used.
315
* Otherwise, a new style is defined.
317
* @param doc the document
318
* @param offset identifies the line to mark
319
* @exception NullPointerException If the <code>doc</code> parameter
320
* is <code>null</code>.
322
* @deprecated since 1.20. Use addAnnotation() instead
325
public static void markCurrent(StyledDocument doc, int offset) {
326
checkDocParameter(doc);
328
Style bp = doc.getStyle(CURRENT_STYLE_NAME);
332
bp = doc.addStyle(CURRENT_STYLE_NAME, null);
338
bp.addAttribute(StyleConstants.ColorConstants.Background, Color.blue);
339
bp.addAttribute(StyleConstants.ColorConstants.Foreground, Color.white);
342
doc.setLogicalStyle(offset, bp);
346
* Mark a line as normal (no special attributes).
347
* This uses the dummy style named {@link #NORMAL_STYLE_NAME}.
348
* This method should be used to undo the effect of {@link #markBreakpoint}, {@link #markError} and {@link #markCurrent}.
349
* @param doc the document
350
* @param offset identified the line to unmark
351
* @exception NullPointerException If the <code>doc</code> parameter
352
* is <code>null</code>.
354
* @deprecated since 1.20. Use addAnnotation() instead
357
public static void markNormal(StyledDocument doc, int offset) {
358
checkDocParameter(doc);
360
Style st = doc.getStyle(NORMAL_STYLE_NAME);
363
st = doc.addStyle(NORMAL_STYLE_NAME, null);
367
doc.setLogicalStyle(offset, st);
371
/** Locks the document to have exclusive access to it.
372
* Documents implementing {@link Lockable} can specify exactly how to do this.
374
* @param doc document to lock
375
* @param run the action to run
376
* @exception NullPointerException If the <code>doc</code> parameter
377
* is <code>null</code>.
379
public static void runAtomic(StyledDocument doc, Runnable run) {
380
checkDocParameter(doc);
382
if (doc instanceof WriteLockable) {
384
((WriteLockable) doc).runAtomic(run);
386
// transfer the runnable to event dispatch thread
393
/** Executes given runnable in "user mode" does not allowing any modifications
394
* to parts of text marked as guarded. The actions should be run as "atomic" so
395
* either happen all at once or none at all (if a guarded block should be modified).
397
* @param doc document to modify
398
* @param run runnable to run in user mode that will have exclusive access to modify the document
399
* @exception NullPointerException If the <code>doc</code> parameter
400
* is <code>null</code>.
401
* @exception BadLocationException if a modification of guarded text occured
402
* and that is why no changes to the document has been done.
404
public static void runAtomicAsUser(StyledDocument doc, Runnable run)
405
throws BadLocationException {
406
checkDocParameter(doc);
408
if (doc instanceof WriteLockable) {
410
((WriteLockable) doc).runAtomicAsUser(run);
412
// transfer the runnable to event dispatch thread
419
/** Helper method for checking document parameter validity.
420
* @exception NullPointerException If the <code>doc</code> parameter
421
* is <code>null</code>. */
422
private static void checkDocParameter(Document doc) {
424
throw new NullPointerException("Invalid doc parameter. Document may not be null!"); // NOI18N
428
/** Find a way to print a given document.
429
* If the document implements the correct interface(s) then the document is returned,
430
* else a default {@link java.awt.print.Printable} is used as a wrapper. In this last case it is useful
431
* to implement {@link NbDocument.Printable} to describe how to print in terms of
432
* attributed characters, rather than specifying the complete page layout from scratch.
434
* @param doc the document to find printing support for
435
* @return an object that is instance of eith {@link java.awt.print.Printable} or {@link java.awt.print.Pageable}
437
public static Object findPageable(StyledDocument doc) {
438
if (doc instanceof java.awt.print.Pageable) {
440
} else if (doc instanceof java.awt.print.Printable) {
443
return new DefaultPrintable(doc);
447
/** Add annotation to the document. For annotation of whole line
448
* the length parameter can be ignored (specify value -1).
449
* @param doc the document which will be annotated
450
* @param startPos position which represent begining
451
* of the annotated text
452
* @param length length of the annotated text. If -1 is specified
453
* the whole line will be annotated
454
* @param annotation annotation which is attached to this text
457
public static void addAnnotation(
458
final StyledDocument doc, final Position startPos, final int length, final Annotation annotation
460
if (!(doc instanceof Annotatable)) {
464
if (SwingUtilities.isEventDispatchThread()) {
465
((Annotatable) doc).addAnnotation(startPos, length, annotation);
467
SwingUtilities.invokeLater(
470
((Annotatable) doc).addAnnotation(startPos, length, annotation);
477
/** Removal of added annotation.
478
* @param doc the annotated document
479
* @param annotation annotation which is going to be removed
482
public static void removeAnnotation(final StyledDocument doc, final Annotation annotation) {
483
if (!(doc instanceof Annotatable)) {
487
if (SwingUtilities.isEventDispatchThread()) {
488
((Annotatable) doc).removeAnnotation(annotation);
490
SwingUtilities.invokeLater(
493
((Annotatable) doc).removeAnnotation(annotation);
500
/** Specialized version of document that knows how to lock the document
501
* for complex modifications.
503
public interface WriteLockable extends Document {
504
/** Executes given runnable in lock mode of the document.
505
* In this mode, all redrawing is stopped and the caller has exclusive
506
* access to all modifications to the document.
508
* By definition there should be only one locker at a time. Sample implementation, if you are extending {@link AbstractDocument}:
513
* r.run();<br>
515
* writeUnlock();<br>
519
* @param r runnable to run while locked
521
* @see NbDocument#runAtomic
523
public void runAtomic(Runnable r);
525
/** Executes given runnable in "user mode" does not allowing any modifications
526
* to parts of text marked as guarded. The actions should be run as "atomic" so
527
* either happen all at once or none at all (if a guarded block should be modified).
529
* @param r runnable to run in user mode that will have exclusive access to modify the document
530
* @exception BadLocationException if a modification of guarded text occured
531
* and that is why no changes to the document has been done.
533
public void runAtomicAsUser(Runnable r) throws BadLocationException;
536
/** Document which may support styled text printing.
537
* Any document that wishes to support special formatting while printing
538
* can implement this interface and provide a <code>AttributedCharacterIterator</code>
539
* specifying colors, fonts, etc.
541
public interface Printable extends Document {
542
/** Get an attributed character iterator for the document, so that it may be printed.
543
* <p>For a convenient way to do this, you may use {@link AttributedCharacters#iterator a simple implementation}
545
* attributed character list.
546
* @return list of <code>AttributedCharacterIterator</code>s to be used for printing
548
* @see NbDocument#findPageable */
549
public java.text.AttributedCharacterIterator[] createPrintIterators();
552
/** Enhanced version of document that provides better support for
553
* holding and working with biased positions. It adds one new method
554
* {@link #createPosition} that creates
555
* a position that moves either to the left or to the right when an insertion
556
* is performed at it.
558
* If a document implements this interface, the new method is
559
* used in {@link NbDocument#createPosition}.
560
* If not, special support for the position is created.
562
public interface PositionBiasable extends Document {
563
/** Creates position with a bias. If the bias is {@link javax.swing.text.Position.Bias#Backward}
564
* then if an insert occures at the position, the text is inserted
565
* after the position. If the bias is {@link javax.swing.text.Position.Bias#Forward <code>Forward</code>}, then the text is
566
* inserted before the position.
568
* @param offset the offset for the position
569
* @param bias the bias to use for the position
570
* @exception BadLocationException if the offset is invalid
572
* @see NbDocument#createPosition
574
public Position createPosition(int offset, Position.Bias bias)
575
throws BadLocationException;
578
/** Enabled documents to add special UI components to their Editor pane.
579
* If this interface is implemented by the Editor document, it can be used
580
* to add other components (such as toolbars) to the pane.
582
public interface CustomEditor extends Document {
583
/** Create a whole editor component over the given <code>JEditorPane</code>.
584
* The implementation should generally add some kind of scrolling
585
* support to the given <code>JEditorPane</code> (typically with scrollbars),
586
* possibly some other components
587
* according to the desired layout,
588
* and return the resulting container.
589
* @param j editor pane over which the resulting component
591
* @return component encapsulating the pane and all other
592
* custom UI components
594
public Component createEditor(JEditorPane j);
598
* Enabled documents to add special UI toolbar components to their Editor pane.
600
public interface CustomToolbar extends Document {
602
* Implementation shall return a toolbar for the document. Preferably non-floatable.
604
public JToolBar createToolbar(JEditorPane j);
607
/** Enhanced version of document which is capable of
608
* attaching/detaching of annotations. It is guaranteed that
609
* annotations are added/removed to document only in AWT thread.
612
public interface Annotatable extends Document {
613
/** Add annotation to the document. For annotation of whole line
614
* the length parameter can be ignored (specify value -1).
615
* @param startPos position which represent begining
616
* of the annotated text
617
* @param length length of the annotated text. If -1 is specified
618
* the whole line will be annotated
619
* @param annotation annotation which is attached to this text */
620
public void addAnnotation(Position startPos, int length, Annotation annotation);
622
/** Removal of added annotation.
623
* @param annotation annotation which is going to be removed */
624
public void removeAnnotation(Annotation annotation);
627
private static final class DocumentRenderer implements Runnable {
628
private static final int FIND_LINE_NUMBER = 0;
629
private static final int FIND_LINE_COLUMN = 1;
630
private static final int FIND_LINE_OFFSET = 2;
631
private StyledDocument doc;
636
DocumentRenderer(int opCode, StyledDocument doc, int argInt) {
637
this.opCode = opCode;
639
this.argInt = argInt;
652
case FIND_LINE_NUMBER: {
653
Element paragraphsParent = findLineRootElement(doc);
654
retInt = paragraphsParent.getElementIndex(argInt); // argInt is offset
659
case FIND_LINE_COLUMN: {
660
Element paragraphsParent = findLineRootElement(doc);
661
int indx = paragraphsParent.getElementIndex(argInt); // argInt is offset
662
retInt = argInt - paragraphsParent.getElement(indx).getStartOffset();
667
case FIND_LINE_OFFSET: {
668
Element paragraphsParent = findLineRootElement(doc);
669
Element line = paragraphsParent.getElement(argInt); // argInt is lineNumber
672
throw new IndexOutOfBoundsException("Index=" + argInt + " is out of bounds."); // NOI18N
675
retInt = line.getStartOffset();
681
throw new IllegalStateException();