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.awt.Graphics;
045: import java.awt.Rectangle;
046: import java.awt.Shape;
047: import java.beans.PropertyChangeListener;
048: import java.util.ArrayList;
049: import java.util.Iterator;
050: import java.util.List;
051: import javax.swing.plaf.TextUI;
052: import javax.swing.text.AbstractDocument;
053: import javax.swing.text.Element;
054: import javax.swing.text.JTextComponent;
055: import javax.swing.text.View;
056: import javax.swing.text.ViewFactory;
057: import org.netbeans.api.editor.fold.Fold;
058: import org.netbeans.api.editor.fold.FoldHierarchy;
059: import org.netbeans.api.editor.fold.FoldUtilities;
060: import org.netbeans.api.editor.fold.FoldHierarchyEvent;
061: import org.netbeans.api.editor.fold.FoldHierarchyListener;
062: import org.netbeans.lib.editor.view.GapDocumentView;
063: import org.netbeans.editor.view.spi.LockView;
064:
065: /**
066: * View of the whole document supporting the code folding.
067: *
068: * @author Miloslav Metelka
069: */
070: /* package */class DrawEngineDocView extends GapDocumentView implements
071: FoldHierarchyListener, PropertyChangeListener {
072:
073: private static final boolean debugRebuild = Boolean
074: .getBoolean("netbeans.debug.editor.view.rebuild"); // NOI18N
075:
076: private FoldHierarchy foldHierarchy;
077: /** Editor UI listening to */
078: private EditorUI editorUI;
079:
080: private Iterator collapsedFoldIterator;
081: private Fold collapsedFold;
082: private int collapsedFoldStartOffset;
083: private int collapsedFoldEndOffset;
084:
085: private boolean collapsedFoldsInPresentViews;
086:
087: private boolean estimatedSpanResetInitiated;
088:
089: DrawEngineDocView(Element elem) {
090: super (elem);
091:
092: setEstimatedSpan(true);
093: }
094:
095: public void setParent(View parent) {
096: if (parent != null) { // start listening
097: JTextComponent component = (JTextComponent) parent
098: .getContainer();
099: foldHierarchy = FoldHierarchy.get(component);
100: foldHierarchy.addFoldHierarchyListener(this );
101: TextUI tui = component.getUI();
102: if (tui instanceof BaseTextUI) {
103: editorUI = ((BaseTextUI) tui).getEditorUI();
104: if (editorUI != null) {
105: editorUI.addPropertyChangeListener(this );
106: }
107: }
108: }
109:
110: super .setParent(parent);
111:
112: if (parent == null) {
113: foldHierarchy.removeFoldHierarchyListener(this );
114: foldHierarchy = null;
115: if (editorUI != null) {
116: editorUI.removePropertyChangeListener(this );
117: editorUI = null;
118: }
119: }
120: }
121:
122: protected void attachListeners() {
123: if (foldHierarchy != null) {
124: }
125: }
126:
127: private FoldHierarchy getFoldHierarchy() {
128: return foldHierarchy;
129: }
130:
131: protected boolean useCustomReloadChildren() {
132: return true;
133: }
134:
135: /**
136: * Find next collapsed fold in the given offset range.
137: * @param lastCollapsedFold last collapsed fold returned by this method.
138: * @param startOffset starting offset of the area in which the collapsed folds
139: * should be searched.
140: * @param endOffset ending offset of the area in which the collapsed folds
141: * should be searched.
142: */
143: protected Fold nextCollapsedFold() {
144: while (true) {
145: Fold fold = collapsedFoldIterator.hasNext() ? (Fold) collapsedFoldIterator
146: .next()
147: : null;
148:
149: // Check whether the fold is not past the doc
150: if (fold != null) {
151: collapsedFoldStartOffset = fold.getStartOffset();
152: collapsedFoldEndOffset = fold.getEndOffset();
153: /* Ignore the empty folds as they would make up
154: * no visible view anyway.
155: * Although the fold hierarchy removes the empty views
156: * automatically it may happen that the document listener
157: * that the fold hierarchy attaches may not be notified yet.
158: */
159: if (collapsedFoldStartOffset == collapsedFoldEndOffset) {
160: if (debugRebuild) {
161: /*DEBUG*/System.err
162: .println("GapBoxView.nextCollapsedFold(): ignored empty fold " // NOI18N
163: + fold);
164: }
165: continue; // skip empty fold
166: }
167:
168: if (collapsedFoldEndOffset > getDocument().getLength()) {
169: /* The fold is past the end of the document.
170: * If a document is going to be switched in the component
171: * the view hierarchy may be notified sooner
172: * than fold hierarchy about that change which
173: * can lead to this state.
174: * That fold is ignored together with the rest of the folds
175: * that would follow it.
176: */
177: fold = null;
178: }
179: }
180:
181: if (fold != null) {
182: collapsedFoldsInPresentViews = true;
183: }
184:
185: return fold;
186: }
187: }
188:
189: /**
190: * Extra initialization for custom reload of children.
191: */
192: protected void initCustomReloadChildren(FoldHierarchy hierarchy,
193: int startOffset, int endOffset) {
194: collapsedFoldIterator = FoldUtilities.collapsedFoldIterator(
195: hierarchy, startOffset, endOffset);
196: collapsedFold = nextCollapsedFold();
197: }
198:
199: /**
200: * Free any resources required for custom reload of children.
201: */
202: protected void finishCustomReloadChildren(FoldHierarchy hierarchy) {
203: collapsedFoldIterator = null;
204: collapsedFold = null;
205: }
206:
207: protected void customReloadChildren(int index, int removeLength,
208: int startOffset, int endOffset) {
209: // if removing all the views reset the flag
210: if (index == 0 && removeLength == getViewCount()) {
211: collapsedFoldsInPresentViews = false; // suppose there will be no folds in line views
212: }
213:
214: FoldHierarchy hierarchy = getFoldHierarchy();
215: // Assuming the document lock was already acquired
216: if (hierarchy != null) {
217: hierarchy.lock();
218: try {
219: initCustomReloadChildren(hierarchy, startOffset,
220: endOffset);
221:
222: super .customReloadChildren(index, removeLength,
223: startOffset, endOffset);
224:
225: finishCustomReloadChildren(hierarchy);
226:
227: } finally {
228: hierarchy.unlock();
229: }
230: }
231: }
232:
233: protected View createCustomView(ViewFactory f, int startOffset,
234: int maxEndOffset, int elementIndex) {
235: if (elementIndex == -1) {
236: throw new IllegalStateException(
237: "Need underlying line element structure"); // NOI18N
238: }
239:
240: View view = null;
241:
242: Element elem = getElement();
243: Element lineElem = elem.getElement(elementIndex);
244: boolean createCollapsed = (collapsedFold != null);
245:
246: if (createCollapsed) { // collapsedFold != null
247: int lineElemEndOffset = lineElem.getEndOffset();
248: createCollapsed = (collapsedFoldStartOffset < lineElemEndOffset);
249: if (createCollapsed) { // need to find end of collapsed area
250: Element firstLineElem = lineElem;
251: List foldAndEndLineElemList = new ArrayList();
252:
253: while (true) {
254: int collapsedFoldEndOffset = collapsedFold
255: .getEndOffset();
256: // Find line element index of the line in which the collapsed fold ends
257: while (collapsedFoldEndOffset > lineElemEndOffset) {
258: elementIndex++;
259: lineElem = elem.getElement(elementIndex);
260: lineElemEndOffset = lineElem.getEndOffset();
261: }
262:
263: foldAndEndLineElemList.add(collapsedFold);
264: foldAndEndLineElemList.add(lineElem);
265:
266: collapsedFold = nextCollapsedFold();
267:
268: // No more collapsed or next collapsed does not start on current line
269: if (collapsedFold == null
270: || collapsedFoldStartOffset >= lineElemEndOffset) {
271: break;
272: }
273: }
274:
275: // Create the multi-line-view with collapsed fold(s)
276: view = new FoldMultiLineView(firstLineElem,
277: foldAndEndLineElemList);
278: }
279: }
280:
281: if (!createCollapsed) {
282: view = f.create(lineElem);
283: }
284:
285: return view;
286: }
287:
288: public void foldHierarchyChanged(FoldHierarchyEvent evt) {
289: LockView lockView = LockView.get(this );
290: lockView.lock();
291: try {
292: layoutLock();
293: try {
294: FoldHierarchy hierarchy = (FoldHierarchy) evt
295: .getSource();
296: if (hierarchy.getComponent().getDocument() != lockView
297: .getDocument()) {
298: // Comonent already has a different document assigned
299: // so this view will be abandoned anyway => do not rebuild
300: // the current chilren because of this change
301: return;
302: }
303:
304: boolean rebuildViews = true;
305: int affectedStartOffset = evt.getAffectedStartOffset();
306: int affectedEndOffset = evt.getAffectedEndOffset();
307:
308: // Check whether it is not a case when there were
309: // no collapsed folds before and no collapsed folds now
310: if (!collapsedFoldsInPresentViews) { // no collapsed folds previously
311: // TODO Could Integer.MAX_VALUE be used?
312: if (FoldUtilities.findCollapsedFold(hierarchy,
313: affectedStartOffset, affectedEndOffset) == null) { // no collapsed folds => no need to rebuild
314: rebuildViews = false;
315: }
316: }
317:
318: if (rebuildViews) {
319: /**
320: * Check the affected offsets against the current document boundaries
321: */
322: int docLength = getDocument().getLength();
323: int rebuildStartOffset = Math.min(
324: affectedStartOffset, docLength);
325: int rebuildEndOffset = Math.min(affectedEndOffset,
326: docLength);
327: offsetRebuild(rebuildStartOffset, rebuildEndOffset);
328: }
329: } finally {
330: updateLayout();
331: layoutUnlock();
332: }
333: } finally {
334: lockView.unlock();
335: }
336: }
337:
338: public void paint(Graphics g, Shape allocation) {
339: java.awt.Component c = getContainer();
340: if (c instanceof javax.swing.text.JTextComponent) {
341: TextUI textUI = ((javax.swing.text.JTextComponent) c)
342: .getUI();
343: if (textUI instanceof BaseTextUI) {
344: ((BaseTextUI) textUI).getEditorUI().paint(g);
345: }
346: }
347:
348: super .paint(g, allocation);
349:
350: // #114712 - set the color to foreground so that the JTextComponent.ComposedTextCaret.paint()
351: // does not render white-on-white.
352: g.setColor(c.getForeground());
353: }
354:
355: public void setSize(float width, float height) {
356: super .setSize(width, height);
357:
358: /* #69446 - disabled estimated span reset
359: // Schedule estimated span reset
360: if (!estimatedSpanResetInitiated && isEstimatedSpan()) {
361: estimatedSpanResetInitiated = true;
362: Timer timer = new Timer(4000, new ActionListener() {
363: public void actionPerformed(ActionEvent evt) {
364: AbstractDocument doc = (AbstractDocument)getDocument();
365: if (doc!=null) {
366: doc.readLock();
367: try {
368: LockView lockView = LockView.get(DrawEngineDocView.this);
369: if (lockView != null) { // if there is no lock view no async layout is done
370: lockView.lock();
371: try {
372: setEstimatedSpan(false);
373: } finally {
374: lockView.unlock();
375: }
376: }
377: } finally {
378: doc.readUnlock();
379: }
380: }
381: }
382: });
383:
384: timer.setRepeats(false);
385: timer.start();
386: }
387: */
388: }
389:
390: protected boolean isChildrenResizeDisabled() {
391: return true;
392: }
393:
394: public void propertyChange(java.beans.PropertyChangeEvent evt) {
395: JTextComponent component = (JTextComponent) getContainer();
396: if (component == null
397: || evt == null
398: || !EditorUI.LINE_HEIGHT_CHANGED_PROP.equals(evt
399: .getPropertyName()))
400: return;
401:
402: AbstractDocument doc = (AbstractDocument) getDocument();
403: if (doc != null) {
404: doc.readLock();
405: try {
406: LockView lockView = LockView.get(this );
407: lockView.lock();
408: try {
409: rebuild(0, getViewCount());
410: } finally {
411: lockView.unlock();
412: }
413: } finally {
414: doc.readUnlock();
415: }
416: component.revalidate();
417: }
418: }
419:
420: public int getYFromPos(int offset, Shape a) {
421: int index = getViewIndex(offset);
422: if (index >= 0) {
423: Shape ca = getChildAllocation(index, a);
424: return (ca instanceof Rectangle) ? ((Rectangle) ca).y
425: : (ca != null) ? ca.getBounds().y : 0;
426: }
427: return 0;
428: }
429:
430: }
|