001: /*
002: * Sun Public License Notice
003: *
004: * The contents of this file are subject to the Sun Public License
005: * Version 1.0 (the "License"). You may not use this file except in
006: * compliance with the License. A copy of the License is available at
007: * http://www.sun.com/
008: *
009: * The Original Code is NetBeans. The Initial Developer of the Original
010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
011: * Microsystems, Inc. All Rights Reserved.
012: */
013:
014: package org.netbeans.editor;
015:
016: import java.awt.Color;
017: import java.awt.Font;
018: import java.text.MessageFormat;
019: import java.util.Enumeration;
020: import java.util.Hashtable;
021:
022: import javax.swing.event.DocumentEvent;
023: import javax.swing.text.AttributeSet;
024: import javax.swing.text.BadLocationException;
025: import javax.swing.text.Element;
026: import javax.swing.text.SimpleAttributeSet;
027: import javax.swing.text.Style;
028: import javax.swing.text.StyleContext;
029: import javax.swing.text.StyledDocument;
030:
031: /**
032: * Extension to the guarded document that implements StyledDocument interface
033: *
034: * @author Miloslav Metelka
035: * @version 1.00
036: */
037:
038: public class GuardedDocument extends BaseDocument implements
039: StyledDocument {
040:
041: /**
042: * Guarded attribute used for specifying that the inserted block will be
043: * guarded.
044: */
045: public static final String GUARDED_ATTRIBUTE = "guarded"; // NOI18N
046:
047: /** AttributeSet with only guarded attribute */
048: public static final SimpleAttributeSet guardedSet = new SimpleAttributeSet();
049:
050: /** AttributeSet with only break-guarded attribute */
051: public static final SimpleAttributeSet unguardedSet = new SimpleAttributeSet();
052:
053: private static final boolean debugAtomic = Boolean
054: .getBoolean("netbeans.debug.editor.atomic"); // NOI18N
055: private static final boolean debugAtomicStack = Boolean
056: .getBoolean("netbeans.debug.editor.atomic.stack"); // NOI18N
057:
058: // Add the attributes to sets
059: static {
060: guardedSet.addAttribute(GUARDED_ATTRIBUTE, Boolean.TRUE);
061: unguardedSet.addAttribute(GUARDED_ATTRIBUTE, Boolean.FALSE);
062: }
063:
064: public static final String FMT_GUARDED_INSERT_LOCALE = "FMT_guarded_insert"; // NOI18N
065: public static final String FMT_GUARDED_REMOVE_LOCALE = "FMT_guarded_remove"; // NOI18N
066:
067: MarkBlockChain guardedBlockChain;
068:
069: /** Break the guarded flag, so inserts/removals over guarded areas will work */
070: boolean breakGuarded;
071:
072: boolean atomicAsUser;
073:
074: /** Style context to hold the styles */
075: protected StyleContext styles;
076:
077: /** Style to layer name mapping */
078: protected Hashtable stylesToLayers;
079:
080: /**
081: * Name of the normal style. The normal style is used to reset the effect of
082: * all styles applied to the line.
083: */
084: protected String normalStyleName;
085:
086: public GuardedDocument(Class kitClass) {
087: this (kitClass, true, new StyleContext());
088: }
089:
090: /**
091: * Create base document with a specified syntax and style context.
092: *
093: * @param kitClass
094: * class used to initialize this document with proper settings
095: * category based on the editor kit for which this document is
096: * created
097: * @param syntax
098: * syntax scanner to use with this document
099: * @param styles
100: * style context to use
101: */
102: public GuardedDocument(Class kitClass, boolean addToRegistry,
103: StyleContext styles) {
104: super (kitClass, addToRegistry);
105: this .styles = styles;
106: stylesToLayers = new Hashtable(5);
107: addLayer(new DrawLayerFactory.GuardedLayer(),
108: DrawLayerFactory.GUARDED_LAYER_VISIBILITY);
109: guardedBlockChain = new MarkBlockChain.LayerChain(this ,
110: DrawLayerFactory.GUARDED_LAYER_NAME);
111: }
112:
113: /** Get the chain of the guarded blocks */
114: public MarkBlockChain getGuardedBlockChain() {
115: return guardedBlockChain;
116: }
117:
118: public boolean isPosGuarded(int pos) {
119: int rel = guardedBlockChain.compareBlock(pos, pos)
120: & MarkBlock.IGNORE_EMPTY;
121: return (rel == MarkBlock.INSIDE_BEGIN || rel == MarkBlock.INNER);
122: }
123:
124: /**
125: * This method is called automatically before the document is updated as
126: * result of removal. This function can throw BadLocationException or its
127: * descendants to stop the ongoing insert from being actually done.
128: *
129: * @param evt
130: * document event containing the change including array of
131: * characters that will be inserted
132: */
133: protected void preInsertCheck(int offset, String text,
134: AttributeSet a) throws BadLocationException {
135: super .preInsertCheck(offset, text, a);
136:
137: int rel = guardedBlockChain.compareBlock(offset, offset)
138: & MarkBlock.IGNORE_EMPTY;
139:
140: if (debugAtomic) {
141: System.err
142: .println("GuardedDocument.beforeInsertUpdate() atomicAsUser=" // NOI18N
143: + atomicAsUser
144: + ", breakGuarded="
145: + breakGuarded // NOI18N
146: + ", inserting text='"
147: + EditorDebug.debugString(text) // NOI18N
148: + "' at offset="
149: + Utilities.debugPosition(this , offset)); // NOI18N
150: if (debugAtomicStack) {
151: Thread.dumpStack();
152: }
153: }
154:
155: if (text.length() > 0 && (rel & MarkBlock.OVERLAP) != 0
156: && rel != MarkBlock.INSIDE_END // guarded
157: // blocks
158: // have
159: // insertAfter
160: // endMark
161: && !(text.charAt(text.length() - 1) == '\n' && rel == MarkBlock.INSIDE_BEGIN)) {
162: if (!breakGuarded || atomicAsUser) {
163: throw new GuardedException(MessageFormat.format(
164: LocaleSupport
165: .getString(FMT_GUARDED_INSERT_LOCALE),
166: new Object[] { new Integer(offset) }), offset);
167: }
168: }
169: }
170:
171: /**
172: * This method is called automatically before the document is updated as
173: * result of removal.
174: */
175: protected void preRemoveCheck(int offset, int len)
176: throws BadLocationException {
177: int rel = guardedBlockChain.compareBlock(offset, offset + len);
178:
179: if (debugAtomic) {
180: System.err
181: .println("GuardedDocument.beforeRemoveUpdate() atomicAsUser=" // NOI18N
182: + atomicAsUser
183: + ", breakGuarded="
184: + breakGuarded // NOI18N
185: + ", removing text='"
186: + EditorDebug.debugChars(getChars(offset,
187: len)) // NOI18N
188: + "'at offset="
189: + Utilities.debugPosition(this , offset)); // NOI18N
190: if (debugAtomicStack) {
191: Thread.dumpStack();
192: }
193: }
194:
195: if ((rel & MarkBlock.OVERLAP) != 0
196: || (rel == MarkBlock.CONTINUE_BEGIN && !(offset == 0 || getChars(
197: offset - 1, 1)[0] == '\n'))) {
198: if (!breakGuarded || atomicAsUser) {
199: // test whether the previous char before removed text is '\n'
200: throw new GuardedException(MessageFormat.format(
201: LocaleSupport
202: .getString(FMT_GUARDED_REMOVE_LOCALE),
203: new Object[] { new Integer(offset) }), offset);
204: }
205: }
206: }
207:
208: public void setCharacterAttributes(int offset, int length,
209: AttributeSet s, boolean replace) {
210: if (((Boolean) s.getAttribute(GUARDED_ATTRIBUTE))
211: .booleanValue() == true) {
212: guardedBlockChain.addBlock(offset, offset + length, false); // no
213: // concat
214: fireChangedUpdate(createDocumentEvent(offset, length,
215: DocumentEvent.EventType.CHANGE));
216: }
217: if (((Boolean) s.getAttribute(GUARDED_ATTRIBUTE))
218: .booleanValue() == false) {
219: guardedBlockChain.removeBlock(offset, offset + length);
220: fireChangedUpdate(createDocumentEvent(offset, length,
221: DocumentEvent.EventType.CHANGE));
222: }
223: }
224:
225: public void runAtomic(Runnable r) {
226: if (debugAtomic) {
227: System.out.println("GuardedDocument.runAtomic() called"); // NOI18N
228: if (debugAtomicStack) {
229: Thread.dumpStack();
230: }
231: }
232:
233: boolean origBreakGuarded = breakGuarded;
234: try {
235: breakGuarded = true;
236: super .runAtomicAsUser(r);
237: } finally {
238: breakGuarded = origBreakGuarded;
239: if (debugAtomic) {
240: System.out
241: .println("GuardedDocument.runAtomic() finished"); // NOI18N
242: }
243: }
244: }
245:
246: public void runAtomicAsUser(Runnable r) {
247: if (debugAtomic) {
248: System.out
249: .println("GuardedDocument.runAtomicAsUser() called"); // NOI18N
250: if (debugAtomicStack) {
251: Thread.dumpStack();
252: }
253: }
254:
255: boolean origAtomicAsUser = atomicAsUser;
256: try {
257: atomicAsUser = true;
258: super .runAtomicAsUser(r);
259: } finally {
260: if (debugAtomic) {
261: System.out
262: .println("GuardedDocument.runAtomicAsUser() finished"); // NOI18N
263: }
264: atomicAsUser = origAtomicAsUser;
265: }
266: }
267:
268: protected BaseDocumentEvent createDocumentEvent(int offset,
269: int length, DocumentEvent.EventType type) {
270: return new GuardedDocumentEvent(this , offset, length, type);
271: }
272:
273: /** Adds style to the document */
274: public Style addStyle(String styleName, Style parent) {
275: String layerName = (String) stylesToLayers.get(styleName);
276: if (layerName == null) {
277: layerName = styleName; // same layer name as style name
278: addStyleToLayerMapping(styleName, layerName);
279: }
280:
281: Style style = styles.addStyle(styleName, parent);
282: if (findLayer(layerName) == null) { // not created by default
283: try {
284: extWriteLock();
285: addStyledLayer(layerName, style);
286: } finally {
287: extWriteUnlock();
288: }
289: }
290: return style;
291: }
292:
293: public void addStyleToLayerMapping(String styleName,
294: String layerName) {
295: stylesToLayers.put(styleName, layerName);
296: }
297:
298: /** Removes style from document */
299: public void removeStyle(String styleName) {
300: styles.removeStyle(styleName);
301: }
302:
303: /** Fetches style previously added */
304: public Style getStyle(String styleName) {
305: return styles.getStyle(styleName);
306: }
307:
308: /**
309: * Set the name for normal style. Normal style is used to reset the effect
310: * of all aplied styles.
311: */
312: public void setNormalStyleName(String normalStyleName) {
313: this .normalStyleName = normalStyleName;
314: }
315:
316: /** Fetches the list of style names */
317: public Enumeration getStyleNames() {
318: return styles.getStyleNames();
319: }
320:
321: /** Change attributes for part of the text. */
322: public void setParagraphAttributes(int offset, int length,
323: AttributeSet s, boolean replace) {
324: // !!! implement
325: }
326:
327: /**
328: * Sets the logical style to use for the paragraph at the given position. If
329: * attributes aren't explicitly set for character and paragraph attributes
330: * they will resolve through the logical style assigned to the paragraph,
331: * which in turn may resolve through some hierarchy completely independent
332: * of the element hierarchy in the document.
333: *
334: * @param pos
335: * the starting position >= 0
336: * @param s
337: * the style to set
338: */
339: public void setLogicalStyle(int pos, Style s) {
340: try {
341: extWriteLock();
342: pos = op.getBOL(pos); // begining of line
343: String layerName = (String) stylesToLayers.get(s.getName());
344: // remove all applied styles
345: DrawLayer[] layerArray = getDrawLayerList().currentLayers();
346: for (int i = 0; i < layerArray.length; i++) {
347: if (layerArray[i] instanceof DrawLayerFactory.StyleLayer) {
348: ((DrawLayerFactory.StyleLayer) layerArray[i]).markChain
349: .removeMark(pos);
350: }
351: }
352: // now set the requested style
353: DrawLayerFactory.StyleLayer styleLayer = (DrawLayerFactory.StyleLayer) findLayer(layerName);
354: if (styleLayer != null) {
355: styleLayer.markChain.addMark(pos);
356: }
357: } catch (BadLocationException e) {
358: // do nothing for invalid positions
359: } finally {
360: extWriteUnlock();
361: }
362: fireChangedUpdate(createDocumentEvent(pos, 0,
363: DocumentEvent.EventType.CHANGE)); // enough
364: // to
365: // say
366: // length
367: // 0
368: }
369:
370: /** Get logical style for position in paragraph */
371: public Style getLogicalStyle(int pos) {
372: try {
373: pos = op.getBOL(pos); // begining of line
374: DrawLayer[] layerArray = getDrawLayerList().currentLayers();
375: for (int i = 0; i < layerArray.length; i++) {
376: DrawLayer layer = layerArray[i];
377: if (layer instanceof DrawLayerFactory.StyleLayer) {
378: if (((DrawLayerFactory.StyleLayer) layer).markChain
379: .isMark(pos)) {
380: return ((DrawLayerFactory.StyleLayer) layer).style;
381: }
382: }
383: }
384: } catch (BadLocationException e) {
385: // do nothing for invalid positions
386: }
387: return getStyle(normalStyleName); // no style found
388: }
389:
390: /**
391: * Gets the element that represents the character that is at the given
392: * offset within the document.
393: *
394: * @param pos
395: * the offset >= 0
396: * @return the element
397: */
398: public Element getCharacterElement(int pos) {
399: return getParagraphElement(pos);
400: }
401:
402: /**
403: * Takes a set of attributes and turn it into a foreground color
404: * specification. This might be used to specify things like brighter, more
405: * hue, etc.
406: *
407: * @param attr
408: * the set of attributes
409: * @return the color
410: */
411: public Color getForeground(AttributeSet attr) {
412: return null; // !!!
413: }
414:
415: /**
416: * Takes a set of attributes and turn it into a background color
417: * specification. This might be used to specify things like brighter, more
418: * hue, etc.
419: *
420: * @param attr
421: * the set of attributes
422: * @return the color
423: */
424: public Color getBackground(AttributeSet attr) {
425: return null; // !!!
426: }
427:
428: /**
429: * Takes a set of attributes and turn it into a font specification. This can
430: * be used to turn things like family, style, size, etc into a font that is
431: * available on the system the document is currently being used on.
432: *
433: * @param attr
434: * the set of attributes
435: * @return the font
436: */
437: public Font getFont(AttributeSet attr) {
438: return null; // !!!
439: }
440:
441: protected DrawLayer addStyledLayer(String layerName, Style style) {
442: if (layerName != null) {
443: try {
444: int indColon = layerName.indexOf(':');
445: int layerVisibility = Integer.parseInt(layerName
446: .substring(indColon + 1));
447: DrawLayer layer = new DrawLayerFactory.StyleLayer(
448: layerName, this , style);
449:
450: addLayer(layer, layerVisibility);
451: return layer;
452:
453: } catch (NumberFormatException e) {
454: // wrong name, let it pass
455: }
456: }
457: return null;
458: }
459:
460: public String toStringDetail() {
461: return super .toStringDetail() + getDrawLayerList()
462: + ",\nGUARDED blocks:\n" + guardedBlockChain; // NOI18N
463: }
464:
465: }
|