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.
42
package org.netbeans.editor;
44
import java.text.MessageFormat;
45
import java.util.Hashtable;
46
import java.util.Enumeration;
47
import java.awt.Color;
49
import javax.swing.text.BadLocationException;
50
import javax.swing.text.StyledDocument;
51
import javax.swing.text.Style;
52
import javax.swing.text.Element;
53
import javax.swing.text.AttributeSet;
54
import javax.swing.text.StyleContext;
55
import javax.swing.event.DocumentEvent;
56
import javax.swing.text.Position;
57
import javax.swing.text.SimpleAttributeSet;
58
import org.openide.util.NbBundle;
60
* Extension to the guarded document that implements
61
* StyledDocument interface
63
* @author Miloslav Metelka
67
public class GuardedDocument extends BaseDocument
68
implements StyledDocument {
70
/** Guarded attribute used for specifying that the inserted block
73
public static final String GUARDED_ATTRIBUTE = "guarded"; // NOI18N
75
/** AttributeSet with only guarded attribute */
76
public static final SimpleAttributeSet guardedSet = new SimpleAttributeSet();
78
/** AttributeSet with only break-guarded attribute */
79
public static final SimpleAttributeSet unguardedSet = new SimpleAttributeSet();
81
private static final boolean debugAtomic = Boolean.getBoolean("netbeans.debug.editor.atomic"); // NOI18N
82
private static final boolean debugAtomicStack = Boolean.getBoolean("netbeans.debug.editor.atomic.stack"); // NOI18N
84
// Add the attributes to sets
86
guardedSet.addAttribute(GUARDED_ATTRIBUTE, Boolean.TRUE);
87
unguardedSet.addAttribute(GUARDED_ATTRIBUTE, Boolean.FALSE);
90
public static final String FMT_GUARDED_INSERT_LOCALE = "FMT_guarded_insert"; // NOI18N
91
public static final String FMT_GUARDED_REMOVE_LOCALE = "FMT_guarded_remove"; // NOI18N
93
MarkBlockChain guardedBlockChain;
95
/** Break the guarded flag, so inserts/removals over guarded areas will work */
100
/** Style context to hold the styles */
101
protected StyleContext styles;
103
/** Style to layer name mapping */
104
protected Hashtable stylesToLayers;
106
/** Name of the normal style. The normal style is used to reset the effect
107
* of all styles applied to the line.
109
protected String normalStyleName;
111
public GuardedDocument(Class kitClass) {
112
this(kitClass, true, new StyleContext());
115
/** Create base document with a specified syntax and style context.
116
* @param kitClass class used to initialize this document with proper settings
117
* category based on the editor kit for which this document is created
118
* @param syntax syntax scanner to use with this document
119
* @param styles style context to use
121
public GuardedDocument(Class kitClass, boolean addToRegistry, StyleContext styles) {
122
super(kitClass, addToRegistry);
123
this.styles = styles;
124
stylesToLayers = new Hashtable(5);
125
guardedBlockChain = new MarkBlockChain(this) {
126
protected Mark createBlockStartMark() {
127
MarkFactory.ContextMark startMark = new MarkFactory.ContextMark(Position.Bias.Forward, false);
131
protected Mark createBlockEndMark() {
132
MarkFactory.ContextMark endMark = new MarkFactory.ContextMark(Position.Bias.Backward, false);
138
/** Get the chain of the guarded blocks */
139
public MarkBlockChain getGuardedBlockChain() {
140
return guardedBlockChain;
143
public boolean isPosGuarded(int pos) {
144
int rel = guardedBlockChain.compareBlock(pos, pos) & MarkBlock.IGNORE_EMPTY;
145
return (rel == MarkBlock.INSIDE_BEGIN || rel == MarkBlock.INNER);
148
/** This method is called automatically before the document
149
* is updated as result of removal. This function can throw
150
* BadLocationException or its descendants to stop the ongoing
151
* insert from being actually done.
152
* @param evt document event containing the change including array
153
* of characters that will be inserted
155
protected void preInsertCheck(int offset, String text, AttributeSet a)
156
throws BadLocationException {
157
super.preInsertCheck(offset, text, a);
159
int rel = guardedBlockChain.compareBlock(offset, offset) & MarkBlock.IGNORE_EMPTY;
162
System.err.println("GuardedDocument.beforeInsertUpdate() atomicAsUser=" // NOI18N
163
+ atomicAsUser + ", breakGuarded=" + breakGuarded // NOI18N
164
+ ", inserting text='" + EditorDebug.debugString(text) // NOI18N
165
+ "' at offset=" + Utilities.debugPosition(this, offset)); // NOI18N
166
if (debugAtomicStack) {
171
if (text.length() > 0
172
&& (rel & MarkBlock.OVERLAP) != 0
173
&& rel != MarkBlock.INSIDE_END // guarded blocks have insertAfter endMark
174
&& !(text.charAt(text.length() - 1) == '\n'
175
&& rel == MarkBlock.INSIDE_BEGIN)
177
if (!breakGuarded || atomicAsUser) {
178
throw new GuardedException(
179
MessageFormat.format(
180
NbBundle.getBundle(BaseKit.class).getString(FMT_GUARDED_INSERT_LOCALE),
191
/** This method is called automatically before the document
192
* is updated as result of removal.
194
protected void preRemoveCheck(int offset, int len)
195
throws BadLocationException {
196
int rel = guardedBlockChain.compareBlock(offset, offset + len);
199
System.err.println("GuardedDocument.beforeRemoveUpdate() atomicAsUser=" // NOI18N
200
+ atomicAsUser + ", breakGuarded=" + breakGuarded // NOI18N
201
+ ", removing text='" + EditorDebug.debugChars(getChars(offset, len)) // NOI18N
202
+ "'at offset=" + Utilities.debugPosition(this, offset)); // NOI18N
203
if (debugAtomicStack) {
208
if ((rel & MarkBlock.OVERLAP) != 0
209
|| (rel == MarkBlock.CONTINUE_BEGIN
210
&& !(offset == 0 || getChars(offset - 1, 1)[0] == '\n'))
212
if (!breakGuarded || atomicAsUser) {
213
// test whether the previous char before removed text is '\n'
214
throw new GuardedException(
215
MessageFormat.format(
216
NbBundle.getBundle(BaseKit.class).getString(FMT_GUARDED_REMOVE_LOCALE),
227
public void setCharacterAttributes(int offset, int length, AttributeSet attribs, boolean replace) {
228
if (((Boolean)attribs.getAttribute(GUARDED_ATTRIBUTE)).booleanValue() == true) {
229
guardedBlockChain.addBlock(offset, offset + length, false); // no concat
230
fireChangedUpdate(getDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE, attribs));
232
if (((Boolean)attribs.getAttribute(GUARDED_ATTRIBUTE)).booleanValue() == false) {
233
guardedBlockChain.removeBlock(offset, offset + length);
234
fireChangedUpdate(getDocumentEvent(offset, length, DocumentEvent.EventType.CHANGE, attribs));
238
public void runAtomic(Runnable r) {
240
System.out.println("GuardedDocument.runAtomic() called"); // NOI18N
241
if (debugAtomicStack) {
246
boolean completed = false;
248
boolean origBreakGuarded = breakGuarded;
254
breakGuarded = origBreakGuarded;
263
System.out.println("GuardedDocument.runAtomic() finished"); // NOI18N
268
public void runAtomicAsUser(Runnable r) {
270
System.out.println("GuardedDocument.runAtomicAsUser() called"); // NOI18N
271
if (debugAtomicStack) {
276
boolean completed = false;
278
boolean origAtomicAsUser = atomicAsUser;
284
atomicAsUser = origAtomicAsUser;
293
System.out.println("GuardedDocument.runAtomicAsUser() finished"); // NOI18N
298
protected BaseDocumentEvent createDocumentEvent(int offset, int length,
299
DocumentEvent.EventType type) {
300
return new GuardedDocumentEvent(this, offset, length, type);
303
/** Adds style to the document */
304
public Style addStyle(String styleName, Style parent) {
305
String layerName = (String)stylesToLayers.get(styleName);
306
if (layerName == null) {
307
layerName = styleName; // same layer name as style name
308
addStyleToLayerMapping(styleName, layerName);
311
Style style = styles.addStyle(styleName, parent);
312
if (findLayer(layerName) == null) { // not created by default
315
addStyledLayer(layerName, style);
323
public void addStyleToLayerMapping(String styleName, String layerName) {
324
stylesToLayers.put(styleName, layerName);
327
/** Removes style from document */
328
public void removeStyle(String styleName) {
329
styles.removeStyle(styleName);
332
/** Fetches style previously added */
333
public Style getStyle(String styleName) {
334
return styles.getStyle(styleName);
337
/** Set the name for normal style. Normal style is used to reset the effect
338
* of all aplied styles.
340
public void setNormalStyleName(String normalStyleName) {
341
this.normalStyleName = normalStyleName;
344
/** Fetches the list of style names */
345
public Enumeration getStyleNames() {
346
return styles.getStyleNames();
349
/** Change attributes for part of the text. */
350
public void setParagraphAttributes(int offset, int length, AttributeSet s,
356
* Sets the logical style to use for the paragraph at the
357
* given position. If attributes aren't explicitly set
358
* for character and paragraph attributes they will resolve
359
* through the logical style assigned to the paragraph, which
360
* in turn may resolve through some hierarchy completely
361
* independent of the element hierarchy in the document.
363
* @param pos the starting position >= 0
364
* @param s the style to set
366
public void setLogicalStyle(int pos, Style s) {
369
pos = Utilities.getRowStart(this, pos);
370
String layerName = (String)stylesToLayers.get(s.getName());
371
// remove all applied styles
372
DrawLayer[] layerArray = getDrawLayerList().currentLayers();
373
for (int i = 0; i < layerArray.length; i++) {
374
if (layerArray[i] instanceof DrawLayerFactory.StyleLayer) {
375
((DrawLayerFactory.StyleLayer)layerArray[i]).markChain.removeMark(pos);
378
// now set the requested style
379
DrawLayerFactory.StyleLayer styleLayer
380
= (DrawLayerFactory.StyleLayer)findLayer(layerName);
381
if (styleLayer != null) {
382
styleLayer.markChain.addMark(pos);
384
fireChangedUpdate(getDocumentEvent(
385
pos, 0, DocumentEvent.EventType.CHANGE, null)); // enough to say length 0
386
} catch (BadLocationException e) {
387
// do nothing for invalid positions
393
/** Get logical style for position in paragraph */
394
public Style getLogicalStyle(int pos) {
396
pos = Utilities.getRowStart(this, pos);
397
DrawLayer[] layerArray = getDrawLayerList().currentLayers();
398
for (int i = 0; i < layerArray.length; i++) {
399
DrawLayer layer = layerArray[i];
400
if (layer instanceof DrawLayerFactory.StyleLayer) {
401
if (((DrawLayerFactory.StyleLayer)layer).markChain.isMark(pos)) {
402
return ((DrawLayerFactory.StyleLayer)layer).style;
406
return getStyle(normalStyleName); // no style found
407
} catch (BadLocationException e) {
413
* Gets the element that represents the character that
414
* is at the given offset within the document.
416
* @param pos the offset >= 0
417
* @return the element
419
public Element getCharacterElement(int pos) {
420
return getParagraphElement(pos);
425
* Takes a set of attributes and turn it into a foreground color
426
* specification. This might be used to specify things
427
* like brighter, more hue, etc.
429
* @param attr the set of attributes
432
public Color getForeground(AttributeSet attr) {
437
* Takes a set of attributes and turn it into a background color
438
* specification. This might be used to specify things
439
* like brighter, more hue, etc.
441
* @param attr the set of attributes
444
public Color getBackground(AttributeSet attr) {
449
* Takes a set of attributes and turn it into a font
450
* specification. This can be used to turn things like
451
* family, style, size, etc into a font that is available
452
* on the system the document is currently being used on.
454
* @param attr the set of attributes
457
public Font getFont(AttributeSet attr) {
458
return new Font("Default",Font.BOLD,12); // NOI18N
462
* Using of <code>DrawLayer</code>s has been deprecated.
464
* @deprecated Please use Highlighting SPI instead, for details see
465
* <a href="@org-netbeans-modules-editor-lib2@/overview-summary.html">Editor Library 2</a>.
467
protected DrawLayer addStyledLayer(String layerName, Style style) {
468
if (layerName != null) {
470
int indColon = layerName.indexOf(':');
471
int layerVisibility = Integer.parseInt(layerName.substring(indColon + 1));
472
DrawLayer layer = new DrawLayerFactory.StyleLayer(layerName, this, style);
474
addLayer(layer, layerVisibility);
477
} catch (NumberFormatException e) {
478
// wrong name, let it pass
484
public String toStringDetail() {
485
return super.toStringDetail()
487
+ ",\nGUARDED blocks:\n" + guardedBlockChain; // NOI18N