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.*;
045: import java.awt.event.ActionEvent;
046: import java.beans.PropertyChangeListener;
047: import java.beans.PropertyChangeEvent;
048: import java.util.List;
049: import java.util.Iterator;
050: import javax.swing.text.*;
051: import javax.swing.event.DocumentListener;
052: import javax.swing.event.DocumentEvent;
053: import javax.swing.plaf.TextUI;
054: import javax.swing.JComponent;
055: import javax.swing.JEditorPane;
056: import javax.swing.SwingUtilities;
057: import javax.swing.Action;
058: import javax.swing.UIManager;
059: import javax.swing.plaf.basic.BasicTextUI;
060: import javax.swing.text.Position.Bias;
061: import org.netbeans.modules.editor.lib2.EditorApiPackageAccessor;
062: import org.netbeans.editor.view.spi.LockView;
063:
064: /**
065: * Text UI implementation
066: *
067: * @author Miloslav Metelka, Martin Roskanin
068: * @version 1.00
069: */
070:
071: public class BaseTextUI extends BasicTextUI implements
072: PropertyChangeListener, DocumentListener,
073: SettingsChangeListener {
074:
075: /* package */static final String PROP_DEFAULT_CARET_BLINK_RATE = "nbeditor-default-swing-caret-blink-rate"; //NOI18N
076:
077: /** Extended UI */
078: private EditorUI editorUI;
079:
080: private boolean foldingEnabled;
081:
082: private boolean needsRefresh = false;
083:
084: /** ID of the component in registry */
085: int componentID = -1;
086:
087: private AbstractDocument lastDocument;
088:
089: /** Instance of the <tt>GetFocusedComponentAction</tt> */
090: private static final GetFocusedComponentAction gfcAction = new GetFocusedComponentAction();
091:
092: public BaseTextUI() {
093: }
094:
095: protected String getPropertyPrefix() {
096: return "EditorPane"; //NOI18N
097: }
098:
099: public static JTextComponent getFocusedComponent() {
100: return gfcAction.getFocusedComponent2();
101: }
102:
103: protected boolean isRootViewReplaceNecessary() {
104: boolean replaceNecessary = false;
105:
106: Document doc = getComponent().getDocument();
107: if (doc != lastDocument) {
108: replaceNecessary = true;
109: }
110:
111: return replaceNecessary;
112: }
113:
114: protected void rootViewReplaceNotify() {
115: // update the newly used document
116: lastDocument = (AbstractDocument) getComponent().getDocument();
117: }
118:
119: /** Called when the model of component is changed */
120: protected void modelChanged() {
121: JTextComponent component = getComponent();
122: // [TODO] assert (component != null);
123: Document doc = component.getDocument();
124:
125: if (doc != null && !(doc instanceof AbstractDocument)) {
126: // This UI works with AbstractDocument document instances only
127: return; // return silently
128: }
129: AbstractDocument adoc = (AbstractDocument) doc;
130:
131: // Possibly rebuild fold hierarchy prior to rebuilding views.
132: // Views have optimization in fold hierarchy rebuild listening
133: // so that the actual views rebuild is only done once.
134: // Readlock on both last and current docs.
135: /* if (doc != lastDocument) {
136: if (lastDocument != null) {
137: lastDocument.readLock();
138: }
139: try {
140: if (adoc != null) {
141: adoc.readLock();
142: }
143: try {
144: FoldHierarchySpi.get(component).rebuild();
145: } finally {
146: if (adoc != null) {
147: adoc.readUnlock();
148: }
149: }
150: } finally {
151: if (lastDocument != null) {
152: lastDocument.readUnlock();
153: }
154: }
155: }
156: */
157:
158: if (doc != null) {
159: ViewFactory f = getRootView(component).getViewFactory();
160: BaseKit kit = (BaseKit) getEditorKit(component);
161:
162: component.removeAll();
163:
164: if (isRootViewReplaceNecessary()) {
165: rootViewReplaceNotify();
166: Element elem = doc.getDefaultRootElement();
167: View v = f.create(elem);
168: setView(v);
169: }
170:
171: component.revalidate();
172:
173: // Execute actions related to document installaction into the component
174: Settings.KitAndValue[] kv = Settings.getValueHierarchy(kit
175: .getClass(),
176: SettingsNames.DOC_INSTALL_ACTION_NAME_LIST);
177: for (int i = kv.length - 1; i >= 0; i--) {
178: List actList = (List) kv[i].value;
179: actList = kit.translateActionNameList(actList); // translate names to actions
180: if (actList != null) {
181: for (Iterator iter = actList.iterator(); iter
182: .hasNext();) {
183: Action a = (Action) iter.next();
184: a.actionPerformed(new ActionEvent(component,
185: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
186: }
187: }
188: }
189: }
190: }
191:
192: /* XXX - workaround bugfix of issue #45487 and #45678
193: * The hack can be removed if JDK bug
194: * http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5067948
195: * will be fixed.
196: */
197: protected void installKeyboardActions() {
198: String mapName = getPropertyPrefix() + ".actionMap"; //NOI18N
199: // XXX - workaround bugfix of issue #45487
200: // Because the ActionMap is cached in method BasicTextUI.getActionMap()
201: // the property 'mapName' is set to null to force new actionMap creation
202: UIManager.getLookAndFeelDefaults().put(mapName, null);
203: UIManager.getDefaults().put(mapName, null); //#45678
204: super .installKeyboardActions();
205: }
206:
207: /** Installs the UI for a component. */
208: public void installUI(JComponent c) {
209: super .installUI(c);
210:
211: if (!(c instanceof JTextComponent)) {
212: return;
213: }
214:
215: JTextComponent component = getComponent();
216:
217: // set margin
218: Object value = Settings.getValue(Utilities
219: .getKitClass(component), SettingsNames.MARGIN);
220: Insets margin = (value instanceof Insets) ? (Insets) value
221: : null;
222: component.setMargin(margin);
223:
224: getEditorUI().installUI(component);
225: Object foldingEnabledBoolean = Settings.getValue(Utilities
226: .getKitClass(component),
227: SettingsNames.CODE_FOLDING_ENABLE);
228: foldingEnabled = foldingEnabledBoolean instanceof Boolean ? ((Boolean) foldingEnabledBoolean)
229: .booleanValue()
230: : false;
231: component.putClientProperty(SettingsNames.CODE_FOLDING_ENABLE,
232: foldingEnabledBoolean);
233:
234: Settings.addSettingsChangeListener(this );
235:
236: // attach to the model and component
237: //component.addPropertyChangeListener(this); already done in super class
238: if (component.getClientProperty(UIWatcher.class) == null) {
239: UIWatcher uiWatcher = new UIWatcher(this .getClass());
240: component.addPropertyChangeListener(uiWatcher);
241: component.putClientProperty(UIWatcher.class, uiWatcher);
242: }
243:
244: BaseKit kit = (BaseKit) getEditorKit(component);
245: ViewFactory vf = kit.getViewFactory();
246: // Create and attach caret
247: Caret defaultCaret = component.getCaret();
248: Caret caret = kit.createCaret();
249: component.setCaretColor(Color.black); // will be changed by settings later
250: component.setCaret(caret);
251: component.putClientProperty(PROP_DEFAULT_CARET_BLINK_RATE,
252: defaultCaret.getBlinkRate());
253:
254: // assign blink rate
255: int br = SettingsUtil.getInteger(Utilities
256: .getKitClass(component),
257: SettingsNames.CARET_BLINK_RATE,
258: SettingsDefaults.defaultCaretBlinkRate.intValue());
259: if (br == -1) {
260: br = defaultCaret.getBlinkRate();
261: }
262: caret.setBlinkRate(br);
263:
264: // Create document
265: /* BaseDocument doc = Utilities.getDocument(component);
266: if (doc != null) {
267: modelChanged(null, doc);
268: }
269: */
270:
271: SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED,
272: null);
273:
274: Registry.addComponent(component);
275: Registry.activate(component);
276: EditorApiPackageAccessor.get().register(component);
277: component.setCursor(Cursor
278: .getPredefinedCursor(Cursor.TEXT_CURSOR));
279: }
280:
281: /** Deinstalls the UI for a component */
282: public void uninstallUI(JComponent c) {
283: super .uninstallUI(c);
284:
285: Settings.removeSettingsChangeListener(this );
286: //c.removePropertyChangeListener(this);
287:
288: if (c instanceof JTextComponent) {
289: JTextComponent comp = (JTextComponent) c;
290: BaseDocument doc = Utilities.getDocument(comp);
291: if (doc != null) {
292: doc.removeDocumentListener(this );
293: }
294:
295: comp.setKeymap(null);
296: comp.setCaret(null);
297:
298: getEditorUI().uninstallUI(comp);
299: Registry.removeComponent(comp);
300: }
301:
302: // Clear the editorUI so it will be recreated according to the kit
303: // of the component for which the installUI() is called
304: editorUI = null;
305: }
306:
307: public int getYFromPos(int pos) throws BadLocationException {
308: JTextComponent component = getComponent();
309: if (component != null) {
310: Document doc = component.getDocument();
311: if (doc instanceof AbstractDocument) {
312: ((AbstractDocument) doc).readLock();
313: }
314: try {
315: View rootView = getRootView(component);
316: if (rootView.getViewCount() > 0) {
317: View view = rootView.getView(0);
318: if (view instanceof LockView) {
319: LockView lockView = (LockView) view;
320: lockView.lock();
321: try {
322: DrawEngineDocView docView = (DrawEngineDocView) view
323: .getView(0);
324: Rectangle alloc = getVisibleEditorRect();
325: if (alloc != null) {
326: rootView.setSize(alloc.width,
327: alloc.height);
328: return docView.getYFromPos(pos, alloc);
329: }
330: } finally {
331: lockView.unlock();
332: }
333: }
334: }
335: } finally {
336: if (doc instanceof AbstractDocument) {
337: ((AbstractDocument) doc).readUnlock();
338: }
339: }
340: }
341: Rectangle ret = modelToView(component, pos);
342: return (ret == null) ? 0 : ret.y;
343: }
344:
345: public int getPosFromY(int y) throws BadLocationException {
346: return viewToModel(getComponent(), 0, y);
347: }
348:
349: public int getBaseX(int y) {
350: return getEditorUI().getTextMargin().left;
351: }
352:
353: public int viewToModel(JTextComponent c, int x, int y) {
354: return viewToModel(c, new Point(x, y));
355: }
356:
357: @Override
358: public void damageRange(JTextComponent t, int p0, int p1,
359: Bias p0Bias, Bias p1Bias) {
360: View rootView = getRootView(getComponent());
361: boolean doDamageRange = true;
362: if (rootView.getViewCount() > 0) {
363: View view = rootView.getView(0);
364: if (view instanceof LockView) {
365: LockView lockView = (LockView) view;
366: lockView.lock();
367: try {
368: DrawEngineDocView docView = (DrawEngineDocView) view
369: .getView(0);
370: doDamageRange = docView.checkDamageRange(p0, p1,
371: p0Bias, p1Bias);
372: } finally {
373: lockView.unlock();
374: }
375: }
376: }
377: if (doDamageRange) {
378: super .damageRange(t, p0, p1, p0Bias, p1Bias);
379: }
380: }
381:
382: /** Next visually represented model location where caret can be placed.
383: * This version works without placing read lock on the document.
384: */
385: public int getNextVisualPositionFrom(JTextComponent t, int pos,
386: Position.Bias b, int direction, Position.Bias[] biasRet)
387: throws BadLocationException {
388: if (biasRet == null) {
389: biasRet = new Position.Bias[1];
390: biasRet[0] = Position.Bias.Forward;
391: }
392: return super .getNextVisualPositionFrom(t, pos, b, direction,
393: biasRet);
394: }
395:
396: /** Fetches the EditorKit for the UI.
397: *
398: * @return the component capabilities
399: */
400: public EditorKit getEditorKit(JTextComponent c) {
401: JEditorPane pane = (JEditorPane) getComponent();
402: return (pane == null) ? null : pane.getEditorKit();
403: }
404:
405: /** Get extended UI. This is called from views to get correct extended UI. */
406: public EditorUI getEditorUI() {
407: if (editorUI == null) {
408: JTextComponent c = getComponent();
409: BaseKit kit = (BaseKit) getEditorKit(c);
410: if (kit != null) {
411: editorUI = kit.createEditorUI();
412: editorUI.initLineHeight(c);
413: }
414: }
415: return editorUI;
416: }
417:
418: /**
419: * This method gets called when a bound property is changed.
420: * We are looking for document changes on the component.
421: */
422: public void propertyChange(PropertyChangeEvent evt) {
423: String propName = evt.getPropertyName();
424: if ("document".equals(propName)) { // NOI18N
425: BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument) ? (BaseDocument) evt
426: .getOldValue()
427: : null;
428:
429: if (oldDoc != null) {
430: oldDoc.removeDocumentListener(this );
431: }
432:
433: BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument) ? (BaseDocument) evt
434: .getNewValue()
435: : null;
436:
437: if (newDoc != null) {
438: newDoc.addDocumentListener(this );
439: Registry.activate(newDoc); // Activate the new document
440: }
441: } else if ("ancestor".equals(propName)) { // NOI18N
442: JTextComponent comp = (JTextComponent) evt.getSource();
443: if (comp.isDisplayable() && editorUI != null
444: && editorUI.hasExtComponent()) {
445: // #41209: In case extComponent was retrieved set the ancestorOverride
446: // to true and expect that the editor kit that installed
447: // this UI will be deinstalled explicitly.
448: if (!Boolean.TRUE.equals(comp
449: .getClientProperty("ancestorOverride"))) { // NOI18N
450: comp.putClientProperty("ancestorOverride",
451: Boolean.TRUE); // NOI18N
452: }
453: }
454: }
455: }
456:
457: /** Insert to document notification. */
458: public void insertUpdate(DocumentEvent evt) {
459: try {
460: BaseDocumentEvent bevt = (BaseDocumentEvent) evt;
461: EditorUI editorUI = getEditorUI();
462: int y = getYFromPos(evt.getOffset());
463: int lineHeight = editorUI.getLineHeight();
464: int syntaxY = getYFromPos(bevt.getSyntaxUpdateOffset());
465: // !!! patch for case when DocMarksOp.eolMark is at the end of document
466: if (bevt.getSyntaxUpdateOffset() == evt.getDocument()
467: .getLength()) {
468: syntaxY += lineHeight;
469: }
470: if (getComponent().isShowing()) {
471: editorUI.repaint(y, Math.max(lineHeight, syntaxY - y));
472: }
473: } catch (BadLocationException ex) {
474: Utilities.annotateLoggable(ex);
475: }
476: }
477:
478: /** Remove from document notification. */
479: public void removeUpdate(DocumentEvent evt) {
480: try {
481: BaseDocumentEvent bevt = (BaseDocumentEvent) evt;
482: EditorUI editorUI = getEditorUI();
483: int y = getYFromPos(evt.getOffset());
484: int lineHeight = editorUI.getLineHeight();
485: int syntaxY = getYFromPos(bevt.getSyntaxUpdateOffset());
486: // !!! patch for case when DocMarksOp.eolMark is at the end of document
487: if (bevt.getSyntaxUpdateOffset() == evt.getDocument()
488: .getLength()) {
489: syntaxY += lineHeight;
490: }
491: if (getComponent().isShowing()) {
492: editorUI.repaint(y, Math.max(lineHeight, syntaxY - y));
493: }
494:
495: } catch (BadLocationException ex) {
496: Utilities.annotateLoggable(ex);
497: }
498: }
499:
500: /** The change in document notification.
501: *
502: * @param evt The change notification from the currently associated document.
503: */
504: public void changedUpdate(DocumentEvent evt) {
505: if (evt instanceof BaseDocumentEvent) {
506: BaseDocumentEvent bdevt = (BaseDocumentEvent) evt;
507: BaseDocument doc = (BaseDocument) bdevt.getDocument();
508: String layerName = bdevt.getDrawLayerName();
509: if (layerName != null) {
510: getEditorUI().addLayer(doc.findLayer(layerName),
511: bdevt.getDrawLayerVisibility());
512: } else { //temp
513: try {
514: JTextComponent comp = getComponent();
515: if (comp != null && comp.isShowing()) {
516: getEditorUI().repaintBlock(evt.getOffset(),
517: evt.getOffset() + evt.getLength());
518: }
519: } catch (BadLocationException ex) {
520: Utilities.annotateLoggable(ex);
521: }
522: }
523: }
524: }
525:
526: /** Creates a view for an element.
527: *
528: * @param elem the element
529: * @return the newly created view or null
530: */
531: public View create(Element elem) {
532: String kind = elem.getName();
533: /*
534: if (foldingEnabled){
535: Element parent = elem.getParentElement();
536: if (parent!=null){
537: int index = parent.getElementIndex(elem.getStartOffset());
538: if (index >=3 && index <=6){
539: return new CollapsedView(parent.getElement(3), parent.getElement(6));
540: }
541: }
542: }
543: */
544:
545: if (kind != null) {
546: if (kind.equals(AbstractDocument.ContentElementName)) {
547: return new LabelView(elem);
548: } else if (kind
549: .equals(AbstractDocument.ParagraphElementName)) {
550: // System.out.println("creating DrawEngineLineView for elem=" + elem);
551: return new DrawEngineLineView(elem);//.createFragment(elem.getStartOffset()+10,elem.getStartOffset()+30);
552: } else if (kind.equals(AbstractDocument.SectionElementName)) {
553: // return new LockView(new EditorUIBoxView(elem, View.Y_AXIS));
554: // System.out.println("creating DrawEngineDocView for elem=" + elem);
555: // return new DrawEngineDocView(getComponent()); // EditorUIBoxView(elem, View.Y_AXIS);
556: return new LockView(new DrawEngineDocView(elem)); // EditorUIBoxView(elem, View.Y_AXIS);
557: } else if (kind.equals(StyleConstants.ComponentElementName)) {
558: return new ComponentView(elem);
559: } else if (kind.equals(StyleConstants.IconElementName)) {
560: return new IconView(elem);
561: }
562: }
563:
564: // default to text display
565: return new DrawEngineLineView(elem);
566: }
567:
568: /** Creates a view for an element.
569: * @param elem the element
570: * @param p0 the starting offset >= 0
571: * @param p1 the ending offset >= p0
572: * @return the view
573: */
574: public View create(Element elem, int p0, int p1) {
575: return null;
576: }
577:
578: /** Specifies that some preference has changed. */
579: public void preferenceChanged(boolean width, boolean height) {
580: modelChanged();
581: }
582:
583: public void invalidateStartY() {
584: // no longer available
585: }
586:
587: public void settingsChange(SettingsChangeEvent evt) {
588: JTextComponent component = getComponent();
589: if (component == null)
590: return;
591:
592: if (evt == null
593: || Utilities.getKitClass(component) != evt
594: .getKitClass())
595: return;
596:
597: if (SettingsNames.CODE_FOLDING_ENABLE.equals(evt
598: .getSettingName())) {
599: Boolean foldingEnabledBoolean = (Boolean) Settings
600: .getValue(evt.getKitClass(),
601: SettingsNames.CODE_FOLDING_ENABLE);
602: foldingEnabled = foldingEnabledBoolean.booleanValue();
603: component.putClientProperty(
604: SettingsNames.CODE_FOLDING_ENABLE,
605: foldingEnabledBoolean);
606: needsRefresh = true;
607: Utilities.runInEventDispatchThread(new Runnable() {
608: public void run() {
609: refresh();
610: }
611: });
612: }
613: }
614:
615: boolean isFoldingEnabled() {
616: return foldingEnabled;
617: }
618:
619: protected void refresh() {
620: if (getComponent().isShowing() && needsRefresh) {
621: modelChanged();
622: needsRefresh = false;
623: }
624: }
625:
626: private static class GetFocusedComponentAction extends TextAction {
627:
628: private GetFocusedComponentAction() {
629: super ("get-focused-component"); // NOI18N
630: }
631:
632: public void actionPerformed(ActionEvent evt) {
633: }
634:
635: JTextComponent getFocusedComponent2() {
636: return super .getFocusedComponent();
637: }
638:
639: }
640:
641: static void uninstallUIWatcher(JTextComponent c) {
642: UIWatcher uiWatcher = (UIWatcher) c
643: .getClientProperty(UIWatcher.class);
644: if (uiWatcher != null) {
645: c.removePropertyChangeListener(uiWatcher);
646: c.putClientProperty(UIWatcher.class, null);
647: }
648: }
649:
650: /** Class that returns back BaseTextUI after its change
651: * by changing look-and-feel.
652: */
653: static class UIWatcher implements PropertyChangeListener {
654:
655: private Class uiClass;
656:
657: UIWatcher(Class uiClass) {
658: this .uiClass = uiClass;
659: }
660:
661: public void propertyChange(PropertyChangeEvent evt) {
662: Object newValue = evt.getNewValue();
663: if ("UI".equals(evt.getPropertyName())
664: && (newValue != null)
665: && !(newValue instanceof BaseTextUI)) {
666: JTextComponent c = (JTextComponent) evt.getSource();
667: EditorKit kit = ((TextUI) newValue).getEditorKit(c);
668: if (kit instanceof BaseKit) {
669: // BaseKit but not BaseTextUI -> restore BaseTextUI
670: try {
671: c.setUI((BaseTextUI) uiClass.newInstance());
672: } catch (InstantiationException e) {
673: } catch (IllegalAccessException e) {
674: }
675: }
676: }
677: }
678:
679: }
680:
681: }
|