001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.editor;
043:
044: import java.lang.ref.WeakReference;
045: import java.util.ArrayList;
046: import java.util.Collections;
047: import java.util.List;
048: import java.util.logging.Level;
049: import java.util.logging.Logger;
050: import javax.swing.JEditorPane;
051: import javax.swing.SwingUtilities;
052: import javax.swing.text.AbstractDocument;
053: import javax.swing.text.AttributeSet;
054: import javax.swing.text.BadLocationException;
055: import javax.swing.text.Caret;
056: import javax.swing.text.Document;
057: import javax.swing.text.Element;
058: import javax.swing.text.JTextComponent;
059: import javax.swing.text.Position;
060: import javax.swing.text.Position.Bias;
061: import javax.swing.text.SimpleAttributeSet;
062: import javax.swing.text.View;
063: import org.netbeans.api.editor.settings.AttributesUtilities;
064: import org.netbeans.editor.ext.ExtCaret;
065: import org.netbeans.editor.view.spi.LockView;
066: import org.netbeans.modules.editor.lib2.highlighting.CaretBasedBlockHighlighting.CaretRowHighlighting;
067: import org.netbeans.modules.editor.lib2.highlighting.HighlightingManager;
068: import org.netbeans.modules.editor.lib2.highlighting.HighlightingSpiPackageAccessor;
069: import org.netbeans.modules.editor.lib2.highlighting.HighlightsLayerAccessor;
070: import org.netbeans.modules.editor.lib2.highlighting.HighlightsLayerFilter;
071: import org.netbeans.spi.editor.highlighting.HighlightsChangeEvent;
072: import org.netbeans.spi.editor.highlighting.HighlightsChangeListener;
073: import org.netbeans.spi.editor.highlighting.HighlightsContainer;
074: import org.netbeans.spi.editor.highlighting.HighlightsLayer;
075: import org.netbeans.spi.editor.highlighting.HighlightsSequence;
076: import org.openide.util.WeakListeners;
077:
078: /**
079: *
080: * @author vita
081: */
082: /* package */final class HighlightingDrawLayer extends
083: DrawLayer.AbstractLayer implements HighlightsChangeListener,
084: AtomicLockListener {
085: // -J-Dorg.netbeans.editor.HighlightingDrawLayer.level=FINE
086: private static final Logger LOG = Logger
087: .getLogger(HighlightingDrawLayer.class.getName());
088:
089: private static final String LAYER_A_NAME = "org-netbeans-lib-editor-nview-HighlightingDrawLayer/A"; //NOI18N
090: // Using the original name for the caret row highlighting, some clients use it to remove the layer.
091: private static final String LAYER_B_NAME = ExtCaret.HIGHLIGHT_ROW_LAYER_NAME;
092: private static final String LAYER_C_NAME = "org-netbeans-lib-editor-nview-HighlightingDrawLayer/C"; //NOI18N
093:
094: private static final HighlightsLayerFilter FILTER_A = new HighlightsLayerFilter() {
095: public List<? extends HighlightsLayer> filterLayers(
096: List<? extends HighlightsLayer> layers) {
097: ArrayList<HighlightsLayer> filteredLayers = new ArrayList<HighlightsLayer>();
098: boolean add = false;
099:
100: for (HighlightsLayer layer : layers) {
101: HighlightsLayerAccessor layerAccessor = HighlightingSpiPackageAccessor
102: .get().getHighlightsLayerAccessor(layer);
103:
104: if (CaretRowHighlighting.LAYER_TYPE_ID
105: .equals(layerAccessor.getLayerTypeId())) {
106: add = true;
107: continue;
108: }
109:
110: if (add) {
111: filteredLayers.add(layer);
112: }
113: }
114:
115: return filteredLayers;
116: }
117: }; // End of FILTER_A constant
118:
119: private static final HighlightsLayerFilter FILTER_B = new HighlightsLayerFilter() {
120: public List<? extends HighlightsLayer> filterLayers(
121: List<? extends HighlightsLayer> layers) {
122: ArrayList<HighlightsLayer> filteredLayers = new ArrayList<HighlightsLayer>();
123:
124: for (HighlightsLayer layer : layers) {
125: HighlightsLayerAccessor layerAccessor = HighlightingSpiPackageAccessor
126: .get().getHighlightsLayerAccessor(layer);
127:
128: if (CaretRowHighlighting.LAYER_TYPE_ID
129: .equals(layerAccessor.getLayerTypeId())) {
130: filteredLayers.add(layer);
131: break;
132: }
133: }
134:
135: return filteredLayers;
136: }
137: }; // End of FILTER_B constant
138:
139: private static final HighlightsLayerFilter FILTER_C = new HighlightsLayerFilter() {
140: public List<? extends HighlightsLayer> filterLayers(
141: List<? extends HighlightsLayer> layers) {
142: ArrayList<HighlightsLayer> filteredLayers = new ArrayList<HighlightsLayer>();
143:
144: for (HighlightsLayer layer : layers) {
145: HighlightsLayerAccessor layerAccessor = HighlightingSpiPackageAccessor
146: .get().getHighlightsLayerAccessor(layer);
147:
148: if (CaretRowHighlighting.LAYER_TYPE_ID
149: .equals(layerAccessor.getLayerTypeId())) {
150: break;
151: }
152:
153: filteredLayers.add(layer);
154: }
155:
156: return filteredLayers;
157: }
158: }; // End of FILTER_C constant
159:
160: public static void hookUp(EditorUI eui) {
161: DrawLayer layerA = eui.findLayer(LAYER_A_NAME);
162: if (layerA == null) {
163: layerA = new HighlightingDrawLayer(LAYER_A_NAME, FILTER_A);
164: eui.addLayer(layerA, 10000); // the old caret row draw layer's z-order
165:
166: if (LOG.isLoggable(Level.FINE)) {
167: LOG.fine("Successfully registered layerA in "
168: + simpleToString(eui)); //NOI18N
169: }
170: } else {
171: if (LOG.isLoggable(Level.FINE)) {
172: LOG.fine("LayerA is already registered in "
173: + simpleToString(eui)); //NOI18N
174: }
175: }
176:
177: DrawLayer layerB = eui.findLayer(LAYER_B_NAME);
178: if (layerB == null) {
179: layerB = new HighlightingDrawLayer(LAYER_B_NAME, FILTER_B);
180: eui.addLayer(layerB, 2050); // the old caret row draw layer's z-order
181:
182: if (LOG.isLoggable(Level.FINE)) {
183: LOG.fine("Successfully registered layerB in "
184: + simpleToString(eui)); //NOI18N
185: }
186: } else {
187: if (LOG.isLoggable(Level.FINE)) {
188: LOG.fine("LayerB is already registered in "
189: + simpleToString(eui)); //NOI18N
190: }
191: }
192:
193: DrawLayer layerC = eui.findLayer(LAYER_C_NAME);
194: if (layerC == null) {
195: layerC = new HighlightingDrawLayer(LAYER_C_NAME, FILTER_C);
196: eui.addLayer(layerC, 1000); // the old syntax draw layer's z-order
197:
198: if (LOG.isLoggable(Level.FINE)) {
199: LOG.fine("Successfully registered layerC in "
200: + simpleToString(eui)); //NOI18N
201: }
202: } else {
203: if (LOG.isLoggable(Level.FINE)) {
204: LOG.fine("LayerC is already registered in "
205: + simpleToString(eui)); //NOI18N
206: }
207: }
208: }
209:
210: private final HighlightsLayerFilter filter;
211:
212: private WeakReference<JTextComponent> paneRef = null;
213: private HighlightsContainer highlights = null;
214:
215: private AttributeSet lastAttributeSet = null;
216:
217: // The end-of-line attributes cache
218: private AttributeSet lastEOLAttribs = null;
219: private AttributeSet lastELAttribs = null;
220: private boolean theLittleSpitAtTheBeginningOfAnEmptyLineDrawn = false;
221:
222: /** Index of the last found line element in processOffset(). */
223: private int lastLineIndex;
224:
225: private boolean atomicLockListeningResolved;
226: private boolean inAtomicLock;
227:
228: private Position damageStartPos;
229: private Position damageEndPos;
230:
231: private HighlightingDrawLayer(String name,
232: HighlightsLayerFilter filter) {
233: super (name);
234: this .filter = filter;
235: }
236:
237: public @Override
238: void init(DrawContext ctx) {
239: super .init(ctx);
240:
241: if (highlights == null) {
242: // Initialize
243: JTextComponent pane = ctx.getEditorUI().getComponent();
244:
245: // HACK: the component can be null when printing, so we will just
246: // create a fake JEditorPane
247: if (pane == null) {
248: Document doc = ctx.getEditorUI().getDocument();
249:
250: // Get the document's mime type
251: String mimeType = (String) doc.getProperty("mimeType"); //NOI18N
252: assert mimeType != null : "Document's mime type can't be null: "
253: + doc; //NOI18N
254:
255: // HACK: can't set the kit on fakePane, because it needs to run in AWT, which
256: // the print actions generally don't. So, the fakePane has EditorKit with
257: // the wrong mime type (most likely text/plain). It does not matter much, because
258: // the SyntaxHighlighting and NonLexerSyntaxHighlighting layers are registred for
259: // all mime types and we do not care about other layers.
260: //
261: // // Find the appropriate editor kit
262: // EditorKit kit = MimeLookup.getLookup(MimePath.parse(mimeType)).lookup(EditorKit.class);
263: // assert kit != null : "Can't finde EditorKit for mime type '" + mimeType + "'";
264:
265: // Create a fake pane
266: JEditorPane fakePane = new JEditorPane();
267: // fakePane.setEditorKit(kit);
268: fakePane.setDocument(doc);
269:
270: // Filter out all highlights layers, but syntax highlighting
271: fakePane.putClientProperty("HighlightsLayerIncludes",
272: new String[] {
273: "^.*NonLexerSyntaxHighlighting$",
274: "^.*SyntaxHighlighting$" }); //NOI18N
275:
276: pane = fakePane;
277: }
278: this .paneRef = new WeakReference<JTextComponent>(pane);
279:
280: HighlightingManager hm = HighlightingManager.getInstance();
281: this .highlights = hm.getHighlights(pane, filter);
282: this .highlights.addHighlightsChangeListener(this );
283:
284: if (LOG.isLoggable(Level.FINE)) {
285: if (filter == FILTER_A) {
286: LOG.fine("CHC@"
287: + Integer.toHexString(System
288: .identityHashCode(highlights))
289: + " is for FILTER_A"); //NOI18N
290: } else if (filter == FILTER_B) {
291: LOG.fine("CHC@"
292: + Integer.toHexString(System
293: .identityHashCode(highlights))
294: + " is for FILTER_B"); //NOI18N
295: } else if (filter == FILTER_C) {
296: LOG.fine("CHC@"
297: + Integer.toHexString(System
298: .identityHashCode(highlights))
299: + " is for FILTER_C"); //NOI18N
300: }
301: }
302: }
303:
304: lastAttributeSet = null;
305:
306: // Reset the end-of-line attributes cache
307: lastEOLAttribs = null;
308: lastELAttribs = null;
309: theLittleSpitAtTheBeginningOfAnEmptyLineDrawn = false;
310:
311: if (!atomicLockListeningResolved) {
312: atomicLockListeningResolved = true;
313: BaseDocument doc = ctx.getEditorUI().getDocument();
314: doc.addAtomicLockListener(WeakListeners.create(
315: AtomicLockListener.class, this , doc));
316: }
317: }
318:
319: public boolean isActive(DrawContext ctx, MarkFactory.DrawMark mark) {
320: if (highlights != null) {
321: return processOffset(ctx, false);
322: } else {
323: return false;
324: }
325: }
326:
327: public void updateContext(DrawContext ctx) {
328: if (highlights != null) {
329: if (ctx.isEOL() && ctx.isBOL()) {
330: if (extendsEmptyLine()
331: && !theLittleSpitAtTheBeginningOfAnEmptyLineDrawn) {
332: theLittleSpitAtTheBeginningOfAnEmptyLineDrawn = true;
333: Coloring coloring = Coloring
334: .fromAttributeSet(lastELAttribs);
335: coloring.apply(ctx);
336: } else {
337: if (extendsEOL()) {
338: Coloring coloring = Coloring
339: .fromAttributeSet(lastEOLAttribs);
340: coloring.apply(ctx);
341: }
342: }
343: } else if (ctx.isEOL()) {
344: if (extendsEOL()) {
345: Coloring coloring = Coloring
346: .fromAttributeSet(lastEOLAttribs);
347: coloring.apply(ctx);
348: }
349: } else {
350: processOffset(ctx, true);
351: }
352: }
353: }
354:
355: public @Override
356: boolean extendsEOL() {
357: if (lastEOLAttribs == null && lastAttributeSet != null) {
358: @SuppressWarnings("unchecked")
359: List<AttributeSet> allSets = (List<AttributeSet>) lastAttributeSet
360: .getAttribute("dismantled-structure"); //NOI18N
361: AttributeSet[] arr = filter(allSets != null ? allSets
362: : Collections.singletonList(lastAttributeSet));
363: lastEOLAttribs = arr[0];
364: lastELAttribs = arr[1];
365: }
366:
367: boolean b = lastEOLAttribs != null
368: && lastEOLAttribs != SimpleAttributeSet.EMPTY;
369: if (LOG.isLoggable(Level.FINE) && filter == FILTER_A) {
370: LOG.fine(simpleToString(this ) + ".extendsEOL = " + b);
371: }
372: return b;
373: }
374:
375: public @Override
376: boolean extendsEmptyLine() {
377: if (lastELAttribs == null && lastAttributeSet != null) {
378: @SuppressWarnings("unchecked")
379: List<AttributeSet> allSets = (List<AttributeSet>) lastAttributeSet
380: .getAttribute("dismantled-structure"); //NOI18N
381: AttributeSet[] arr = filter(allSets != null ? allSets
382: : Collections.singletonList(lastAttributeSet));
383: lastEOLAttribs = arr[0];
384: lastELAttribs = arr[1];
385: }
386:
387: boolean b = lastELAttribs != null
388: && lastELAttribs != SimpleAttributeSet.EMPTY;
389: if (LOG.isLoggable(Level.FINE) && filter == FILTER_A) {
390: LOG.fine(simpleToString(this ) + ".extendsEmptyLine = " + b);
391: }
392: return b;
393: }
394:
395: // ----------------------------------------------------------------------
396: // HighlightsChangeListener implementation
397: // ----------------------------------------------------------------------
398:
399: public void highlightChanged(final HighlightsChangeEvent event) {
400: if (LOG.isLoggable(Level.FINEST)) {
401: LOG.finest("BRIDGE-LAYER: changed area ["
402: + event.getStartOffset() + ", "
403: + event.getEndOffset() + "]"); //NOI18N
404:
405: // LOG.log(Level.FINE, "Dumping highlights: {");
406: // HighlightsSequence seq = highlights.getHighlights(0, Integer.MAX_VALUE);
407: // while(seq.moveNext()) {
408: // StringBuilder sb = new StringBuilder();
409: // sb.append(" <");
410: // sb.append(seq.getStartOffset());
411: // sb.append(", ");
412: // sb.append(seq.getEndOffset());
413: // sb.append(", {");
414: //
415: // Enumeration<?> attrNames = seq.getAttributes().getAttributeNames();
416: // while(attrNames.hasMoreElements()) {
417: // Object attrName = attrNames.nextElement();
418: // Object attrValue = seq.getAttributes().getAttribute(attrName);
419: //
420: // sb.append(attrName == null ? "null" : attrName.toString());
421: // sb.append(" = ");
422: // sb.append(attrValue == null ? "null" : attrValue.toString());
423: //
424: // if (attrNames.hasMoreElements()) {
425: // sb.append(", ");
426: // }
427: // }
428: //
429: // sb.append("}>");
430: // LOG.log(Level.FINE, sb.toString());
431: // }
432: // LOG.log(Level.FINE, "--- End of Dumping highlights");
433: }
434:
435: if (event.getStartOffset() == event.getEndOffset()) {
436: return;
437: }
438:
439: if (inAtomicLock) {
440: JTextComponent pane = paneRef.get();
441: if (pane != null) {
442: Document doc = pane.getDocument();
443: if (doc != null) {
444: int startOffset = Math.max(0, Math.min(event
445: .getStartOffset(), doc.getLength()));
446: int endOffset = Math.max(startOffset, Math.min(
447: event.getEndOffset(), doc.getLength()));
448: try {
449: // Only extend the modified area
450: if (damageStartPos == null) {
451: damageStartPos = doc
452: .createPosition(startOffset);
453: }
454: if (damageEndPos == null) {
455: damageEndPos = doc
456: .createPosition(endOffset);
457: }
458:
459: if (startOffset < damageStartPos.getOffset()) {
460: damageStartPos = doc
461: .createPosition(startOffset);
462: }
463: if (endOffset > damageEndPos.getOffset()) {
464: damageEndPos = doc
465: .createPosition(endOffset);
466: }
467: } catch (BadLocationException e) {
468: LOG.log(Level.WARNING,
469: "Cannot set damaged range", e);
470: damageStartPos = null;
471: damageEndPos = null;
472: }
473: }
474: }
475: if (LOG.isLoggable(Level.FINE) && damageStartPos != null
476: && damageEndPos != null) {
477: LOG.fine("highlightsChangeEvent: ["
478: + event.getStartOffset() + ", "
479: + event.getEndOffset() + "], toDAMAGE: ["
480: + damageStartPos.getOffset() + ", "
481: + damageEndPos.getOffset() + "]\n");
482: }
483: return; // Wait for the atomic unlock
484: }
485:
486: invokeDamageRange(event.getStartOffset(), event.getEndOffset());
487: }
488:
489: private void invokeDamageRange(final int startOffset,
490: final int endOffset) {
491: SwingUtilities.invokeLater(new Runnable() {
492: public void run() {
493: setNextActivityChangeOffset(0);
494:
495: JTextComponent pane = paneRef.get();
496: Document d;
497: if (pane != null
498: && (d = pane.getDocument()) instanceof AbstractDocument) {
499: AbstractDocument doc = (AbstractDocument) d;
500: doc.readLock();
501: try {
502: int rangeEnd = Math.min(endOffset, pane
503: .getDocument().getLength() + 1);
504: int rangeStart = startOffset >= rangeEnd ? 0
505: : startOffset;
506:
507: if (rangeStart < rangeEnd) {
508: try {
509: if (LOG.isLoggable(Level.FINE)) {
510: LOG.fine("DamageRange: ["
511: + rangeStart + ", "
512: + rangeEnd + "]\n");
513: }
514: pane.getUI().damageRange(pane,
515: rangeStart, rangeEnd);
516: } catch (Exception e) {
517: LOG.log(Level.INFO,
518: "Can't update view: range = ["
519: + rangeStart + ", "
520: + rangeEnd + "]", e); //NOI18N
521: }
522:
523: try {
524: // XXX: hack, we should use isFixedSize() flag of the layers
525: // this is blindely assuming that caret row highlighting and
526: // higher layers do not change text metrics (eg. font)
527: if (filter == FILTER_C) {
528: notifyViews(pane.getUI()
529: .getRootView(pane),
530: rangeStart, rangeEnd);
531: }
532: } catch (Exception e) {
533: LOG.log(Level.INFO,
534: "Can't reset line views: range = ["
535: + rangeStart + ", "
536: + rangeEnd + "]", e); //NOI18N
537: }
538:
539: // force caret repaint, see #100384
540: // XXX: not very efficient, should only be done for
541: // containers/events that affect metrics
542: Caret caret = pane.getCaret();
543: if (caret instanceof BaseCaret) {
544: ((BaseCaret) caret).changedUpdate(null);
545: }
546: }
547: } finally {
548: doc.readUnlock();
549: }
550: }
551: }
552: });
553: }
554:
555: public void atomicLock(AtomicLockEvent evt) {
556: inAtomicLock = true;
557: }
558:
559: public void atomicUnlock(AtomicLockEvent evt) {
560: inAtomicLock = false;
561: if (damageStartPos != null && damageEndPos != null) { // Accumulated damage range
562: invokeDamageRange(damageStartPos.getOffset(), damageEndPos
563: .getOffset());
564: }
565: damageStartPos = null;
566: damageEndPos = null;
567: }
568:
569: // ----------------------------------------------------------------------
570: // Private implementation
571: // ----------------------------------------------------------------------
572:
573: private void notifyViews(View view, int startOffset, int endOffset) {
574: // Find DrawEngineLineView
575: while (view != null) {
576: int idx = view.getViewIndex(startOffset, Bias.Forward);
577: if (idx != -1) {
578: View v = view.getView(idx);
579: if (v instanceof DrawEngineLineView) {
580: break;
581: } else {
582: view = v;
583: }
584: } else {
585: view = null;
586: }
587: }
588:
589: // If DrawEngineLineView found reset all of them between startOffset, endOffset
590: if (view != null) {
591: LockView lockView = LockView.get(view);
592: lockView.lock();
593: try {
594: int firstViewIdx = view.getViewIndex(startOffset,
595: Bias.Forward);
596: int lastViewIdx = view.getViewIndex(endOffset,
597: Bias.Forward);
598:
599: for (int i = firstViewIdx; i <= lastViewIdx; i++) {
600: View v = view.getView(i);
601: if (v instanceof DrawEngineLineView) {
602: ((DrawEngineLineView) v).highlightsChanged(Math
603: .max(startOffset, v.getStartOffset()),
604: Math.min(endOffset, v.getEndOffset()));
605: }
606: }
607: } finally {
608: lockView.unlock();
609: }
610: }
611: }
612:
613: private int findLineEndOffset(Document doc, int offset) {
614: Element lineRootElement = doc.getDefaultRootElement();
615: int lineIndex = lastLineIndex;
616: if (lineIndex < lineRootElement.getElementCount()) {
617: Element lineElement = lineRootElement.getElement(lineIndex);
618: if (offset >= lineElement.getStartOffset()
619: && offset < lineElement.getEndOffset()) {
620: return lineElement.getEndOffset();
621: }
622: }
623: lineIndex = lineRootElement.getElementIndex(offset);
624: lastLineIndex = lineIndex;
625: return lineRootElement.getElement(lineIndex).getEndOffset();
626: }
627:
628: private boolean processOffset(DrawContext ctx,
629: boolean applyAttributes) {
630: BaseDocument doc = ctx.getEditorUI().getDocument();
631: int currentOffset = ctx.getFragmentOffset();
632: int endOffset = findLineEndOffset(doc, currentOffset);
633:
634: if (endOffset >= doc.getLength()) {
635: endOffset = Integer.MAX_VALUE;
636: }
637:
638: HighlightsSequence hs = highlights.getHighlights(currentOffset,
639: endOffset);
640: boolean hasHighlight = hs.moveNext();
641:
642: if (hasHighlight) {
643: if (hs.getStartOffset() <= currentOffset) {
644: if (applyAttributes) {
645: Coloring coloring = Coloring.fromAttributeSet(hs
646: .getAttributes());
647: coloring.apply(ctx);
648: }
649:
650: lastAttributeSet = hs.getAttributes();
651: setNextActivityChangeOffset(hs.getEndOffset());
652: } else {
653: setNextActivityChangeOffset(hs.getStartOffset());
654: }
655:
656: return true;
657: } else {
658: return false;
659: }
660: }
661:
662: private AttributeSet[] filter(List<AttributeSet> sets) {
663: ArrayList<AttributeSet> eolSets = new ArrayList<AttributeSet>();
664: ArrayList<AttributeSet> elSets = new ArrayList<AttributeSet>();
665:
666: for (AttributeSet set : sets) {
667: Object value = set
668: .getAttribute(HighlightsContainer.ATTR_EXTENDS_EOL);
669:
670: if ((value instanceof Boolean)
671: && ((Boolean) value).booleanValue()) {
672: eolSets.add(set);
673: }
674:
675: value = set
676: .getAttribute(HighlightsContainer.ATTR_EXTENDS_EMPTY_LINE);
677: if ((value instanceof Boolean)
678: && ((Boolean) value).booleanValue()) {
679: elSets.add(set);
680: }
681: }
682:
683: AttributeSet eolAttribs;
684: if (eolSets.size() > 1) {
685: eolAttribs = AttributesUtilities.createComposite(eolSets
686: .toArray(new AttributeSet[eolSets.size()]));
687: } else if (eolSets.size() == 1) {
688: eolAttribs = eolSets.get(0);
689: } else {
690: eolAttribs = SimpleAttributeSet.EMPTY;
691: }
692:
693: AttributeSet elAttribs;
694: if (elSets.size() > 1) {
695: elAttribs = AttributesUtilities.createComposite(elSets
696: .toArray(new AttributeSet[elSets.size()]));
697: } else if (elSets.size() == 1) {
698: elAttribs = elSets.get(0);
699: } else {
700: elAttribs = SimpleAttributeSet.EMPTY;
701: }
702:
703: return new AttributeSet[] { eolAttribs, elAttribs };
704: }
705:
706: private static String simpleToString(Object o) {
707: return o == null ? "null" : o.getClass() + "@"
708: + Integer.toHexString(System.identityHashCode(o)); //NOI18N
709: }
710: }
|