org.netbeans.editor.BaseTextUI Maven / Gradle / Ivy
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.editor;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.PreferenceChangeEvent;
import java.util.prefs.PreferenceChangeListener;
import java.util.prefs.Preferences;
import javax.swing.text.*;
import javax.swing.event.DocumentListener;
import javax.swing.event.DocumentEvent;
import javax.swing.plaf.TextUI;
import javax.swing.JComponent;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.Action;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTextUI;
import javax.swing.text.Position.Bias;
import org.netbeans.api.editor.mimelookup.MimeLookup;
import org.netbeans.api.editor.settings.SimpleValueNames;
import org.netbeans.modules.editor.lib2.EditorApiPackageAccessor;
import org.netbeans.editor.view.spi.LockView;
import org.netbeans.lib.editor.view.GapDocumentView;
import org.netbeans.modules.editor.lib2.EditorPreferencesDefaults;
import org.netbeans.modules.editor.lib2.EditorPreferencesKeys;
import org.netbeans.modules.editor.lib.SettingsConversions;
import org.netbeans.modules.editor.lib.drawing.DrawEngineDocView;
import org.netbeans.modules.editor.lib.drawing.DrawEngineLineView;
import org.netbeans.modules.editor.lib2.view.LockedViewHierarchy;
import org.netbeans.modules.editor.lib2.view.ViewHierarchy;
import org.netbeans.spi.lexer.MutableTextInput;
import org.openide.util.WeakListeners;
/**
* Text UI implementation
*
* @author Miloslav Metelka, Martin Roskanin
* @version 1.00
*/
public class BaseTextUI extends BasicTextUI implements
PropertyChangeListener, DocumentListener, AtomicLockListener {
// -J-Dorg.netbeans.editor.BaseTextUI.level=FINEST
private static final Logger LOG = Logger.getLogger(BaseTextUI.class.getName());
/* package */ static final String PROP_DEFAULT_CARET_BLINK_RATE = "nbeditor-default-swing-caret-blink-rate"; //NOI18N
/**
* How many modifications inside atomic section is considered a lengthy operation (e.g. reformat).
*/
private static final int LENGTHY_ATOMIC_EDIT_THRESHOLD = 80;
/** Extended UI */
private EditorUI editorUI;
private boolean needsRefresh = false;
/** ID of the component in registry */
int componentID = -1;
private Document lastDocument;
private int atomicModCount = -1;
/** Instance of the GetFocusedComponentAction */
private static final GetFocusedComponentAction gfcAction
= new GetFocusedComponentAction();
private Preferences prefs = null;
public BaseTextUI() {
}
protected String getPropertyPrefix() {
return "EditorPane"; //NOI18N
}
public static JTextComponent getFocusedComponent() {
return gfcAction.getFocusedComponent2();
}
protected boolean isRootViewReplaceNecessary() {
boolean replaceNecessary = false;
Document doc = getComponent().getDocument();
if (doc != lastDocument) {
replaceNecessary = true;
}
return replaceNecessary;
}
protected void rootViewReplaceNotify() {
// update the newly used document
lastDocument = getComponent().getDocument();
}
/** Called when the model of component is changed */
protected @Override void modelChanged() {
JTextComponent component = getComponent();
Document doc = component != null ? component.getDocument() : null;
if (component != null && doc != null) {
boolean documentReplaced = isRootViewReplaceNecessary();
component.removeAll();
if (documentReplaced) {
ViewFactory f = getRootView(component).getViewFactory();
rootViewReplaceNotify();
Element elem = doc.getDefaultRootElement();
View v = f.create(elem);
setView(v);
}
component.revalidate();
if (documentReplaced) {
// Execute actions related to document installaction into the component
BaseKit baseKit = Utilities.getKit(component);
if (baseKit != null && prefs != null) {
List actionNamesList = new ArrayList();
String actionNames = prefs.get(EditorPreferencesKeys.DOC_INSTALL_ACTION_NAME_LIST, ""); //NOI18N
for(StringTokenizer t = new StringTokenizer(actionNames, ","); t.hasMoreTokens(); ) { //NOI18N
String actionName = t.nextToken().trim();
actionNamesList.add(actionName);
}
List actionsList = baseKit.translateActionNameList(actionNamesList); // translate names to actions
for(Action a : actionsList) {
a.actionPerformed(new ActionEvent(component, ActionEvent.ACTION_PERFORMED, "")); // NOI18N
}
}
}
}
}
/* XXX - workaround bugfix of issue #45487 and #45678
* The hack can be removed if JDK bug
* http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=5067948
* will be fixed.
*/
protected @Override void installKeyboardActions() {
String mapName = getPropertyPrefix() + ".actionMap"; //NOI18N
// XXX - workaround bugfix of issue #45487
// Because the ActionMap is cached in method BasicTextUI.getActionMap()
// the property 'mapName' is set to null to force new actionMap creation
UIManager.getLookAndFeelDefaults().put(mapName, null);
UIManager.getDefaults().put(mapName, null); //#45678
super.installKeyboardActions();
}
/** Installs the UI for a component. */
public @Override void installUI(JComponent c) {
super.installUI(c);
if (!(c instanceof JTextComponent)) {
return;
}
JTextComponent component = getComponent();
prefs = MimeLookup.getLookup(org.netbeans.lib.editor.util.swing.DocumentUtilities.getMimeType(component)).lookup(Preferences.class);
// set margin
String value = prefs.get(SimpleValueNames.MARGIN, null);
Insets margin = value != null ? SettingsConversions.parseInsets(value) : null;
component.setMargin(margin != null ? margin : EditorUI.NULL_INSETS);
getEditorUI().installUI(component);
// attach to the model and component
//component.addPropertyChangeListener(this); already done in super class
if (component.getClientProperty(UIWatcher.class) == null) {
UIWatcher uiWatcher = new UIWatcher(this.getClass());
component.addPropertyChangeListener(uiWatcher);
component.putClientProperty(UIWatcher.class, uiWatcher);
}
BaseKit kit = (BaseKit)getEditorKit(component);
ViewFactory vf = kit.getViewFactory();
// Create and attach caret
Caret defaultCaret = component.getCaret();
Caret caret = kit.createCaret();
component.setCaretColor(Color.black); // will be changed by settings later
component.setCaret(caret);
component.putClientProperty(PROP_DEFAULT_CARET_BLINK_RATE, defaultCaret.getBlinkRate());
component.setKeymap(kit.getKeymap());
// assign blink rate
int br = prefs.getInt(SimpleValueNames.CARET_BLINK_RATE, -1);
if (br == -1) {
br = defaultCaret.getBlinkRate();
}
caret.setBlinkRate(br);
SwingUtilities.replaceUIInputMap(c, JComponent.WHEN_FOCUSED, null);
EditorApiPackageAccessor.get().register(component);
component.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
}
/** Deinstalls the UI for a component */
public @Override void uninstallUI(JComponent c) {
if (prefs == null) {
// already uninstalled or not installed at all
return;
}
if ((getComponent() != null) && getComponent().getDocument() != null) {
super.uninstallUI(c);
}
prefs = null;
if (c instanceof JTextComponent){
JTextComponent comp = (JTextComponent)c;
BaseDocument doc = Utilities.getDocument(comp);
if (doc != null) {
doc.removeDocumentListener(this);
doc.removeAtomicLockListener(this);
}
comp.setKeymap(null);
comp.setCaret(null);
getEditorUI().uninstallUI(comp);
}
}
/**
* Return y coordinate value for given offset.
*
* @param pos offset in a read-locked document
* @return y
* @throws BadLocationException in case offset is not in document's bounds.
*/
public int getYFromPos(int pos) throws BadLocationException {
JTextComponent component = getComponent();
int y = 0;
if (component != null) {
ViewHierarchy vh = ViewHierarchy.get(component);
LockedViewHierarchy lvh = vh.lock();
try {
y = (int) lvh.modelToY(pos);
} finally {
lvh.unlock();
}
}
return y;
}
public int getPosFromY(int y) throws BadLocationException {
JTextComponent component = getComponent();
int offset = 0;
if (component != null) {
ViewHierarchy vh = ViewHierarchy.get(component);
LockedViewHierarchy lvh = vh.lock();
try {
offset = lvh.viewToModel(0, y, null);
} finally {
lvh.unlock();
}
}
return offset;
}
public int getBaseX(int y) {
return getEditorUI().getTextMargin().left;
}
public int viewToModel(JTextComponent c, int x, int y) {
return viewToModel(c, new Point(x, y));
}
@Override
public void damageRange(JTextComponent t, int p0, int p1, Bias p0Bias, Bias p1Bias) {
View rootView = getRootView(getComponent());
boolean doDamageRange = true;
if (rootView.getViewCount() > 0) {
View view = rootView.getView(0);
if (view instanceof LockView) {
LockView lockView = (LockView) view;
lockView.lock();
try {
GapDocumentView docView = (GapDocumentView)view.getView(0);
doDamageRange = docView.checkDamageRange(p0, p1, p0Bias, p1Bias);
} finally {
lockView.unlock();
}
}
}
if (doDamageRange) {
// Patch since this used to be a fallback and the original views' impl cleared char area at p1 too
Document doc = t.getDocument();
if (doc != null && p1 < doc.getLength()) {
p1++;
}
super.damageRange(t, p0, p1, p0Bias, p1Bias);
}
}
/** Next visually represented model location where caret can be placed.
* This version works without placing read lock on the document.
*/
public @Override int getNextVisualPositionFrom(JTextComponent t, int pos,
Position.Bias b, int direction, Position.Bias[] biasRet)
throws BadLocationException{
if (biasRet == null) {
biasRet = new Position.Bias[1];
biasRet[0] = Position.Bias.Forward;
}
return super.getNextVisualPositionFrom(t, pos, b, direction, biasRet);
}
/** Fetches the EditorKit for the UI.
*
* @return the component capabilities
*/
public @Override EditorKit getEditorKit(JTextComponent c) {
JEditorPane pane = (JEditorPane)getComponent();
return (pane == null) ? null : pane.getEditorKit();
}
/** Get extended UI. This is called from views to get correct extended UI. */
public EditorUI getEditorUI() {
if (editorUI == null) {
JTextComponent c = getComponent();
BaseKit kit = (BaseKit)getEditorKit(c);
if (kit != null) {
editorUI = kit.createEditorUI();
editorUI.initLineHeight(c);
}
}
return editorUI;
}
/**
* This method gets called when a bound property is changed.
* We are looking for document changes on the component.
*/
public @Override void propertyChange(PropertyChangeEvent evt) {
String propName = evt.getPropertyName();
if ("document".equals(propName)) { // NOI18N
BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument)
? (BaseDocument)evt.getOldValue() : null;
if (oldDoc != null) {
oldDoc.removeDocumentListener(this);
oldDoc.removeAtomicLockListener(this);
}
BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument)
? (BaseDocument)evt.getNewValue() : null;
if (newDoc != null) {
newDoc.addDocumentListener(this);
atomicModCount = -1;
newDoc.addAtomicLockListener(this);
}
} else if ("ancestor".equals(propName)) { // NOI18N
JTextComponent comp = (JTextComponent)evt.getSource();
if (comp.isDisplayable() && editorUI != null && editorUI.hasExtComponent()) {
// #41209: In case extComponent was retrieved set the ancestorOverride
// to true and expect that the editor kit that installed
// this UI will be deinstalled explicitly.
if (!Boolean.TRUE.equals(comp.getClientProperty("ancestorOverride"))) { // NOI18N
comp.putClientProperty("ancestorOverride", Boolean.TRUE); // NOI18N
}
}
}
}
/** Insert to document notification. */
public void insertUpdate(DocumentEvent evt) {
checkLengthyAtomicEdit(evt);
// No longer trigger syntax update related repaint
// try {
// BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
// EditorUI eui = getEditorUI();
// int y = getYFromPos(evt.getOffset());
// int lineHeight = eui.getLineHeight();
// int syntaxY = getYFromPos(bevt.getSyntaxUpdateOffset());
// // !!! patch for case when DocMarksOp.eolMark is at the end of document
// if (bevt.getSyntaxUpdateOffset() == evt.getDocument().getLength()) {
// syntaxY += lineHeight;
// }
// if (getComponent().isShowing()) {
// eui.repaint(y, Math.max(lineHeight, syntaxY - y));
// }
// } catch (BadLocationException ex) {
// Utilities.annotateLoggable(ex);
// }
}
/** Remove from document notification. */
public void removeUpdate(DocumentEvent evt) {
checkLengthyAtomicEdit(evt);
// No longer trigger syntax update related repaint
// try {
// BaseDocumentEvent bevt = (BaseDocumentEvent)evt;
// EditorUI eui = getEditorUI();
// int y = getYFromPos(evt.getOffset());
// int lineHeight = eui.getLineHeight();
// int syntaxY = getYFromPos(bevt.getSyntaxUpdateOffset());
// // !!! patch for case when DocMarksOp.eolMark is at the end of document
// if (bevt.getSyntaxUpdateOffset() == evt.getDocument().getLength()) {
// syntaxY += lineHeight;
// }
// if (getComponent().isShowing()) {
// eui.repaint(y, Math.max(lineHeight, syntaxY - y));
// }
//
// } catch (BadLocationException ex) {
// Utilities.annotateLoggable(ex);
// }
}
/** The change in document notification.
*
* @param evt The change notification from the currently associated document.
*/
public void changedUpdate(DocumentEvent evt) {
if (evt instanceof BaseDocumentEvent) {
try {
JTextComponent comp = getComponent();
if (comp!=null && comp.isShowing()) {
getEditorUI().repaintBlock(evt.getOffset(), evt.getOffset() + evt.getLength());
}
} catch (BadLocationException ex) {
Utilities.annotateLoggable(ex);
}
}
}
private void checkLengthyAtomicEdit(DocumentEvent evt) {
if (atomicModCount != -1) {
if (++atomicModCount == LENGTHY_ATOMIC_EDIT_THRESHOLD) {
Document doc = evt.getDocument();
// Deactivate view hierarchy
View rootView = getRootView(getComponent());
View view;
if (rootView != null && rootView.getViewCount() > 0 &&
(view = rootView.getView(0)) instanceof org.netbeans.modules.editor.lib2.view.DocumentView)
{
((org.netbeans.modules.editor.lib2.view.DocumentView)view).updateLengthyAtomicEdit(+1);
}
// Inactivate lexer's token hierarchy
// Commented out due to #200270
// MutableTextInput input = (MutableTextInput) doc.getProperty(MutableTextInput.class);
// if (input != null) {
// input.tokenHierarchyControl().setActive(false);
// }
}
}
}
@Override
public void atomicLock(AtomicLockEvent evt) {
assert (atomicModCount == -1);
atomicModCount = 0;
}
@Override
public void atomicUnlock(AtomicLockEvent evt) {
if (atomicModCount != -1) {
if (atomicModCount >= LENGTHY_ATOMIC_EDIT_THRESHOLD) {
// Activate view hierarchy
View rootView = getRootView(getComponent());
View view;
if (rootView != null && rootView.getViewCount() > 0 &&
(view = rootView.getView(0)) instanceof org.netbeans.modules.editor.lib2.view.DocumentView)
{
((org.netbeans.modules.editor.lib2.view.DocumentView)view).updateLengthyAtomicEdit(-1);
}
// Activate lexer's token hierarchy
Document doc = getComponent().getDocument();
MutableTextInput input = (MutableTextInput) doc.getProperty(MutableTextInput.class);
if (input != null) {
input.tokenHierarchyControl().setActive(true);
}
}
atomicModCount = -1;
}
}
/** Creates a view for an element.
*
* @param elem the element
* @return the newly created view or null
*/
public @Override View create(Element elem) {
String kind = elem.getName();
if (kind != null) {
if (kind.equals(AbstractDocument.ContentElementName)) {
return new LabelView(elem);
} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
// System.out.println("creating DrawEngineLineView for elem=" + elem);
return new DrawEngineLineView(elem);//.createFragment(elem.getStartOffset()+10,elem.getStartOffset()+30);
} else if (kind.equals(AbstractDocument.SectionElementName)) {
// return new LockView(new EditorUIBoxView(elem, View.Y_AXIS));
// System.out.println("creating DrawEngineDocView for elem=" + elem);
// return new DrawEngineDocView(getComponent()); // EditorUIBoxView(elem, View.Y_AXIS);
return new LockView(new DrawEngineDocView(elem)); // EditorUIBoxView(elem, View.Y_AXIS);
} else if (kind.equals(StyleConstants.ComponentElementName)) {
return new ComponentView(elem);
} else if (kind.equals(StyleConstants.IconElementName)) {
return new IconView(elem);
}
}
// default to text display
return new DrawEngineLineView(elem);
}
/** Creates a view for an element.
* @param elem the element
* @param p0 the starting offset >= 0
* @param p1 the ending offset >= p0
* @return the view
*/
public @Override View create(Element elem, int p0, int p1) {
return null;
}
/** Specifies that some preference has changed. */
public void preferenceChanged(boolean width, boolean height) {
modelChanged();
}
public void invalidateStartY() {
// no longer available
}
protected void refresh(){
if (getComponent().isShowing() && needsRefresh){
modelChanged();
needsRefresh = false;
}
}
private static class GetFocusedComponentAction extends TextAction {
private GetFocusedComponentAction() {
super("get-focused-component"); // NOI18N
}
public void actionPerformed(ActionEvent evt) {
}
JTextComponent getFocusedComponent2() {
return super.getFocusedComponent();
}
}
static void uninstallUIWatcher(JTextComponent c) {
UIWatcher uiWatcher = (UIWatcher)c.getClientProperty(UIWatcher.class);
if (uiWatcher != null) {
c.removePropertyChangeListener(uiWatcher);
c.putClientProperty(UIWatcher.class, null);
}
}
/** Class that returns back BaseTextUI after its change
* by changing look-and-feel.
*/
static class UIWatcher implements PropertyChangeListener {
private Class uiClass;
UIWatcher(Class uiClass) {
this.uiClass = uiClass;
}
public void propertyChange(PropertyChangeEvent evt) {
Object newValue = evt.getNewValue();
if ("UI".equals(evt.getPropertyName())) {
LOG.log(Level.FINE, "UI property changed for text component {0}\nOldUI: {1}\nNewUI: {2}\n", new Object[] {
evt.getSource(), evt.getOldValue(), evt.getNewValue()
});
if ((newValue != null) && !(newValue instanceof BaseTextUI)) {
JTextComponent c = (JTextComponent)evt.getSource();
EditorKit kit = ((TextUI)newValue).getEditorKit(c);
if (kit instanceof BaseKit) {
// BaseKit but not BaseTextUI -> restore BaseTextUI
try {
BaseTextUI newUI = (BaseTextUI) uiClass.newInstance();
c.setUI(newUI);
if (evt.getOldValue() instanceof BaseTextUI) {
BaseTextUI oldUI = (BaseTextUI) evt.getOldValue();
if (oldUI.getEditorUI().hasExtComponent()) {
// Remove and re-parent the new ext component in place of original one.
JComponent oldExtComponent = oldUI.getEditorUI().getExtComponent();
Container parent = oldExtComponent.getParent();
if (parent != null) {
// According to CloneableEditor's code add as BorderLayout.CENTER
parent.remove(oldExtComponent);
parent.add(newUI.getEditorUI().getExtComponent());
}
}
}
} catch (InstantiationException e) {
} catch (IllegalAccessException e) {
}
}
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy