0001: /*
0002: * Sun Public License Notice
0003: *
0004: * The contents of this file are subject to the Sun Public License
0005: * Version 1.0 (the "License"). You may not use this file except in
0006: * compliance with the License. A copy of the License is available at
0007: * http://www.sun.com/
0008: *
0009: * The Original Code is NetBeans. The Initial Developer of the Original
0010: * Code is Sun Microsystems, Inc. Portions Copyright 1997-2000 Sun
0011: * Microsystems, Inc. All Rights Reserved.
0012: */
0013:
0014: package org.netbeans.editor;
0015:
0016: import java.awt.BorderLayout;
0017: import java.awt.Color;
0018: import java.awt.Component;
0019: import java.awt.Dimension;
0020: import java.awt.Font;
0021: import java.awt.FontMetrics;
0022: import java.awt.Frame;
0023: import java.awt.Graphics;
0024: import java.awt.Graphics2D;
0025: import java.awt.Insets;
0026: import java.awt.Point;
0027: import java.awt.Rectangle;
0028: import java.awt.event.FocusAdapter;
0029: import java.awt.event.FocusEvent;
0030: import java.beans.PropertyChangeEvent;
0031: import java.beans.PropertyChangeListener;
0032: import java.beans.PropertyChangeSupport;
0033: import java.util.HashMap;
0034: import java.util.Hashtable;
0035: import java.util.Iterator;
0036: import java.util.Map;
0037:
0038: import javax.swing.Action;
0039: import javax.swing.JComponent;
0040: import javax.swing.JPanel;
0041: import javax.swing.JScrollPane;
0042: import javax.swing.JViewport;
0043: import javax.swing.SwingUtilities;
0044: import javax.swing.event.ChangeEvent;
0045: import javax.swing.event.ChangeListener;
0046: import javax.swing.plaf.TextUI;
0047: import javax.swing.text.BadLocationException;
0048: import javax.swing.text.Caret;
0049: import javax.swing.text.JTextComponent;
0050: import javax.swing.text.View;
0051:
0052: /**
0053: * Editor UI for the component. All the additional UI features like advanced
0054: * scrolling, info about fonts, abbreviations, keyword matching are based on
0055: * this class.
0056: *
0057: * @author Miloslav Metelka
0058: * @version 1.00
0059: */
0060: public class EditorUI implements ChangeListener,
0061: PropertyChangeListener, SettingsChangeListener {
0062:
0063: public static final String OVERWRITE_MODE_PROPERTY = "overwriteMode"; // NOI18N
0064:
0065: public static final String COMPONENT_PROPERTY = "component"; // NOI18N
0066:
0067: /**
0068: * Default scrolling type is used for the standard setDot() call. If the
0069: * area is on the screen, it jumps to it, otherwise it centers the requested
0070: * area vertically in the middle of the window and it uses smallest covering
0071: * on the right side.
0072: */
0073: public static final int SCROLL_DEFAULT = 0;
0074:
0075: /**
0076: * Scrolling type used for regular caret moves. The scrollJump is used when
0077: * the caret requests area outside the screen.
0078: */
0079: public static final int SCROLL_MOVE = 1;
0080:
0081: /**
0082: * Scrolling type where the smallest covering for the requested rectangle is
0083: * used. It's useful for going to the end of the line for example.
0084: */
0085: public static final int SCROLL_SMALLEST = 2;
0086:
0087: /**
0088: * Scrolling type for find operations, that can request additional
0089: * configurable area in each direction, so the context around is visible
0090: * too.
0091: */
0092: public static final int SCROLL_FIND = 3;
0093:
0094: private static final Insets NULL_INSETS = new Insets(0, 0, 0, 0);
0095:
0096: private static final Dimension NULL_DIMENSION = new Dimension(0, 0);
0097:
0098: private static final int STYLE_CNT = 4;
0099:
0100: private static final boolean debugUpdateLineHeight = Boolean
0101: .getBoolean("netbeans.debug.editor.updateLineHeight");
0102:
0103: /**
0104: * Map holding the coloring maps for the different languages. It helps to
0105: * minimize the amount of the coloring maps and also save the time necessary
0106: * for their creation.
0107: */
0108: private static final HashMap sharedColoringMaps = new HashMap(57);
0109: private static final SettingsChangeListener clearingListener = new SettingsChangeListener() {
0110: public void settingsChange(SettingsChangeEvent evt) {
0111: // Fired when the Settings are locked
0112: sharedColoringMaps.clear();
0113: }
0114: };
0115:
0116: static {
0117: Settings.addSettingsChangeListener(clearingListener);
0118: }
0119:
0120: /** Component this extended UI is related to. */
0121: private JTextComponent component;
0122:
0123: private JComponent extComponent;
0124:
0125: /** Property change support for firing property changes */
0126: PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(
0127: this );
0128:
0129: /** Document for the case ext ui is constructed without the component */
0130: private BaseDocument printDoc;
0131:
0132: /** Draw layer chain */
0133: private DrawLayerList drawLayerList = new DrawLayerList();
0134:
0135: /** Map holding the [name, coloring] pairs */
0136: private Map coloringMap;
0137:
0138: /**
0139: * Character (or better line) height. Particular view can use a different
0140: * character height however most views will probably use this one.
0141: */
0142: private int lineHeight = 1; // prevent possible division by zero
0143:
0144: private float lineHeightCorrection = 1.0f;
0145:
0146: /** Ascent of the line which is maximum ascent of all the fonts used. */
0147: private int lineAscent;
0148:
0149: /** Width of the space in the default coloring's font */
0150: int defaultSpaceWidth = 1;
0151:
0152: /** Flag to initialize fonts */
0153: private boolean fontsInited;
0154:
0155: /** Should the search words be colored? */
0156: boolean highlightSearch;
0157:
0158: /**
0159: * Enable displaying line numbers. Both this flag and
0160: * <tt>lineNumberVisibleSetting</tt> must be true to have the line numbers
0161: * visible in the window. This flag is false by default. It's turned on
0162: * automatically if the getExtComponent is called.
0163: */
0164: boolean lineNumberEnabled;
0165:
0166: /** This flag corresponds to the LINE_NUMBER_VISIBLE setting. */
0167: boolean lineNumberVisibleSetting;
0168:
0169: /**
0170: * Whether to show line numbers or not. This flag is obtained using bitwise
0171: * AND operation on lineNumberEnabled flag and lineNumberVisibleSetting
0172: * flag.
0173: */
0174: boolean lineNumberVisible;
0175:
0176: /**
0177: * Line number total width with indentation. It includes left and right
0178: * line-number margins and lineNumberDigitWidth * lineNumberMaxDigitCount.
0179: */
0180: int lineNumberWidth;
0181:
0182: /**
0183: * Width of one digit used for line numbering. It's based on the information
0184: * from the line coloring.
0185: */
0186: int lineNumberDigitWidth;
0187:
0188: /** Current maximum count of digits in line number */
0189: int lineNumberMaxDigitCount;
0190:
0191: /** Margin on the left and right side of the line number */
0192: Insets lineNumberMargin;
0193:
0194: /**
0195: * This is the size of the editor as component while the real size of the
0196: * lines edited can be lower. The reason why to use this virtual size is
0197: * that each resizing of the component means revalidating and therefore
0198: * repainting of the whole component.
0199: */
0200: Rectangle virtualSize = new Rectangle();
0201:
0202: // /** This is the increment by which the size of the component
0203: // * is increased.
0204: // */
0205: // Rectangle virtualSizeIncrement = new Rectangle(); !!!
0206:
0207: /** Margin between the line-number bar and the text. */
0208: int textLeftMarginWidth;
0209:
0210: /**
0211: * This is the full margin around the text. The left margin is an addition
0212: * of component's margin and lineNumberWidth and textLeftMarginWidth.
0213: */
0214: Insets textMargin = NULL_INSETS;
0215:
0216: /**
0217: * How much columns/lines to add when the scroll is performed so that the
0218: * component is not scrolled so often. Negative number means portion of the
0219: * extent width/height
0220: */
0221: Insets scrollJumpInsets;
0222:
0223: /**
0224: * How much columns/lines to add when the scroll is performed so that the
0225: * component is not scrolled so often. Negative number means portion of the
0226: * extent width/height
0227: */
0228: Insets scrollFindInsets;
0229:
0230: /**
0231: * Flag saying whether either the width or height in virtualSize were
0232: * updated.
0233: */
0234: boolean virtualSizeUpdated;
0235:
0236: /** Listener to changes in settings */
0237: private PropertyChangeListener settingsListener;
0238:
0239: /** EditorUI properties */
0240: Hashtable props = new Hashtable(11);
0241:
0242: boolean textLimitLineVisible;
0243:
0244: Color textLimitLineColor;
0245:
0246: int textLimitWidth;
0247:
0248: private Rectangle lastExtentBounds = new Rectangle();
0249:
0250: private Dimension componentSizeIncrement = new Dimension();
0251:
0252: private Abbrev abbrev;
0253:
0254: private WordMatch wordMatch;
0255:
0256: private Object componentLock;
0257:
0258: /** Status bar */
0259: StatusBar statusBar;
0260:
0261: private FocusAdapter focusL;
0262:
0263: Map renderingHints;
0264:
0265: /** Glyph gutter used for drawing of annotation glyph icons. */
0266: private GlyphGutter glyphGutter = null;
0267:
0268: /**
0269: * The line numbers can be shown in glyph gutter and therefore it is
0270: * necessary to disable drawing of lines here. During the printing on the
0271: * the other hand, line numbers must be visible.
0272: */
0273: private boolean disableLineNumbers = true;
0274:
0275: /** Left right corner of the JScrollPane */
0276: private JPanel glyphCorner;
0277:
0278: /** Construct extended UI for the use with a text component */
0279: public EditorUI() {
0280: Settings.addSettingsChangeListener(this );
0281:
0282: focusL = new FocusAdapter() {
0283: public void focusGained(FocusEvent evt) {
0284: Registry.activate(getComponent());
0285: }
0286: };
0287:
0288: }
0289:
0290: /** Construct extended UI for printing the given document */
0291: public EditorUI(BaseDocument printDoc) {
0292: this .printDoc = printDoc;
0293:
0294: settingsChange(null);
0295:
0296: setLineNumberEnabled(true);
0297:
0298: updateLineNumberWidth(0);
0299:
0300: drawLayerList.add(printDoc.getDrawLayerList());
0301: }
0302:
0303: /**
0304: * Gets the coloring map that can be shared by the components with the same
0305: * kit. Only the component coloring map is provided.
0306: */
0307: protected static Map getSharedColoringMap(Class kitClass) {
0308: synchronized (Settings.class) { // must sync like this against dedloks
0309: Map cm = (Map) sharedColoringMaps.get(kitClass);
0310: if (cm == null) {
0311: cm = SettingsUtil.getColoringMap(kitClass, false, true);
0312: // Test if there's a default coloring
0313: if (cm.get(SettingsNames.DEFAULT_COLORING) == null) {
0314: cm.put(SettingsNames.DEFAULT_COLORING,
0315: SettingsDefaults.defaultColoring);
0316: }
0317:
0318: sharedColoringMaps.put(kitClass, cm);
0319: }
0320:
0321: return cm;
0322: }
0323: }
0324:
0325: /**
0326: * Called when the <tt>BaseTextUI</tt> is being installed into the
0327: * component.
0328: */
0329: protected void installUI(JTextComponent c) {
0330: synchronized (getComponentLock()) {
0331: this .component = c;
0332: putProperty(COMPONENT_PROPERTY, c);
0333:
0334: // listen on component
0335: component.addPropertyChangeListener(this );
0336: component.addFocusListener(focusL);
0337:
0338: // listen on caret
0339: Caret caret = component.getCaret();
0340: if (caret != null) {
0341: caret.addChangeListener(this );
0342: }
0343:
0344: BaseDocument doc = getDocument();
0345: if (doc != null) {
0346: modelChanged(null, doc);
0347: }
0348: }
0349:
0350: // Make sure all the things depending on non-null component will be
0351: // updated
0352: settingsChange(null);
0353:
0354: // fix for issue #16352
0355: getDefaultColoring().apply(component);
0356: }
0357:
0358: /**
0359: * Called when the <tt>BaseTextUI</tt> is being uninstalled from the
0360: * component.
0361: */
0362: protected void uninstallUI(JTextComponent c) {
0363: synchronized (getComponentLock()) {
0364:
0365: // fix for issue 12996
0366: if (component != null) {
0367:
0368: // stop listening on caret
0369: Caret caret = component.getCaret();
0370: if (caret != null) {
0371: caret.removeChangeListener(this );
0372: }
0373:
0374: // stop listening on component
0375: component.removePropertyChangeListener(this );
0376: component.removeFocusListener(focusL);
0377:
0378: }
0379:
0380: BaseDocument doc = getDocument();
0381: if (doc != null) {
0382: modelChanged(doc, null);
0383: }
0384:
0385: component = null;
0386: putProperty(COMPONENT_PROPERTY, null);
0387:
0388: // Clear the font-metrics cache
0389: FontMetricsCache.clear();
0390: }
0391: }
0392:
0393: /**
0394: * Get the lock assuring the component will not be changed by
0395: * <tt>installUI()</tt> or <tt>uninstallUI()</tt>. It's useful for the
0396: * classes that want to listen for the component change in <tt>EditorUI</tt>.
0397: */
0398: public Object getComponentLock() {
0399: if (componentLock == null) {
0400: componentLock = new ComponentLock();
0401: }
0402: return componentLock;
0403: }
0404:
0405: static class ComponentLock {
0406: };
0407:
0408: public void addPropertyChangeListener(PropertyChangeListener l) {
0409: propertyChangeSupport.addPropertyChangeListener(l);
0410: }
0411:
0412: public void addPropertyChangeListener(String propertyName,
0413: PropertyChangeListener l) {
0414: propertyChangeSupport
0415: .addPropertyChangeListener(propertyName, l);
0416: }
0417:
0418: public void removePropertyChangeListener(PropertyChangeListener l) {
0419: propertyChangeSupport.removePropertyChangeListener(l);
0420: }
0421:
0422: public void removePropertyChangeListener(String propertyName,
0423: PropertyChangeListener l) {
0424: propertyChangeSupport.removePropertyChangeListener(
0425: propertyName, l);
0426: }
0427:
0428: protected final void firePropertyChange(String propertyName,
0429: Object oldValue, Object newValue) {
0430: propertyChangeSupport.firePropertyChange(propertyName,
0431: oldValue, newValue);
0432: }
0433:
0434: public void settingsChange(SettingsChangeEvent evt) {
0435: if (component != null) {
0436: if (Utilities.getKit(component) == null) {
0437: return; // prevent problems if not garbage collected and
0438: // settings changed
0439: }
0440: }
0441:
0442: Class kitClass = getKitClass();
0443: String settingName = (evt != null) ? evt.getSettingName()
0444: : null;
0445:
0446: if (settingName == null
0447: || SettingsNames.LINE_NUMBER_VISIBLE
0448: .equals(settingName)
0449: || SettingsNames.PRINT_LINE_NUMBER_VISIBLE
0450: .equals(settingName)) {
0451: lineNumberVisibleSetting = SettingsUtil
0452: .getBoolean(
0453: kitClass,
0454: (component != null) ? SettingsNames.LINE_NUMBER_VISIBLE
0455: : SettingsNames.PRINT_LINE_NUMBER_VISIBLE,
0456: (component != null) ? SettingsDefaults.defaultLineNumberVisible
0457: : SettingsDefaults.defaultPrintLineNumberVisible);
0458: lineNumberVisible = lineNumberEnabled
0459: && lineNumberVisibleSetting;
0460:
0461: // if this is printing, the drawing of original line numbers must be
0462: // enabled
0463: if (component == null)
0464: disableLineNumbers = false;
0465:
0466: if (disableLineNumbers)
0467: lineNumberVisible = false;
0468: }
0469:
0470: BaseDocument doc = getDocument();
0471: if (doc != null) {
0472:
0473: if (settingName == null
0474: || SettingsNames.LINE_NUMBER_MARGIN
0475: .equals(settingName)) {
0476: Object value = Settings.getValue(kitClass,
0477: SettingsNames.LINE_NUMBER_MARGIN);
0478: lineNumberMargin = (value instanceof Insets) ? (Insets) value
0479: : NULL_INSETS;
0480: }
0481:
0482: if (settingName == null
0483: || SettingsNames.TEXT_LEFT_MARGIN_WIDTH
0484: .equals(settingName)) {
0485: textLeftMarginWidth = SettingsUtil.getInteger(kitClass,
0486: SettingsNames.TEXT_LEFT_MARGIN_WIDTH,
0487: SettingsDefaults.defaultTextLeftMarginWidth);
0488: }
0489:
0490: if (settingName == null
0491: || SettingsNames.LINE_HEIGHT_CORRECTION
0492: .equals(settingName)) {
0493: Object value = Settings.getValue(kitClass,
0494: SettingsNames.LINE_HEIGHT_CORRECTION);
0495: if (!(value instanceof Float)
0496: || ((Float) value).floatValue() < 0) {
0497: value = SettingsDefaults.defaultLineHeightCorrection;
0498: }
0499: lineHeightCorrection = ((Float) value).floatValue();
0500: }
0501:
0502: if (settingName == null
0503: || SettingsNames.TEXT_LIMIT_LINE_VISIBLE
0504: .equals(settingName)) {
0505: textLimitLineVisible = SettingsUtil.getBoolean(
0506: kitClass,
0507: SettingsNames.TEXT_LIMIT_LINE_VISIBLE,
0508: SettingsDefaults.defaultTextLimitLineVisible);
0509: }
0510:
0511: if (settingName == null
0512: || SettingsNames.TEXT_LIMIT_LINE_COLOR
0513: .equals(settingName)) {
0514: Object value = Settings.getValue(kitClass,
0515: SettingsNames.TEXT_LIMIT_LINE_COLOR);
0516: textLimitLineColor = (value instanceof Color) ? (Color) value
0517: : SettingsDefaults.defaultTextLimitLineColor;
0518: }
0519:
0520: if (settingName == null
0521: || SettingsNames.TEXT_LIMIT_WIDTH
0522: .equals(settingName)) {
0523: textLimitWidth = SettingsUtil.getPositiveInteger(
0524: kitClass, SettingsNames.TEXT_LIMIT_WIDTH,
0525: SettingsDefaults.defaultTextLimitWidth);
0526: }
0527:
0528: // component only properties
0529: if (component != null) {
0530: if (settingName == null
0531: || SettingsNames.SCROLL_JUMP_INSETS
0532: .equals(settingName)) {
0533: Object value = Settings.getValue(kitClass,
0534: SettingsNames.SCROLL_JUMP_INSETS);
0535: scrollJumpInsets = (value instanceof Insets) ? (Insets) value
0536: : NULL_INSETS;
0537: }
0538:
0539: if (settingName == null
0540: || SettingsNames.SCROLL_FIND_INSETS
0541: .equals(settingName)) {
0542: Object value = Settings.getValue(kitClass,
0543: SettingsNames.SCROLL_FIND_INSETS);
0544: scrollFindInsets = (value instanceof Insets) ? (Insets) value
0545: : NULL_INSETS;
0546: }
0547:
0548: if (settingName == null
0549: || SettingsNames.COMPONENT_SIZE_INCREMENT
0550: .equals(settingName)) {
0551: Object value = Settings.getValue(kitClass,
0552: SettingsNames.COMPONENT_SIZE_INCREMENT);
0553: componentSizeIncrement = (value instanceof Dimension) ? (Dimension) value
0554: : NULL_DIMENSION;
0555: }
0556:
0557: if (settingName == null
0558: || SettingsNames.RENDERING_HINTS
0559: .equals(settingName)) {
0560: Object value = Settings.getValue(kitClass,
0561: SettingsNames.RENDERING_HINTS);
0562: renderingHints = (value instanceof Map) ? (Map) value
0563: : null;
0564: }
0565:
0566: if (settingName == null
0567: || SettingsNames.CARET_COLOR_INSERT_MODE
0568: .equals(settingName)
0569: || SettingsNames.CARET_COLOR_OVERWRITE_MODE
0570: .equals(settingName)) {
0571: Boolean b = (Boolean) getProperty(OVERWRITE_MODE_PROPERTY);
0572: Color caretColor;
0573: if (b == null || !b.booleanValue()) {
0574: Object value = Settings.getValue(kitClass,
0575: SettingsNames.CARET_COLOR_INSERT_MODE);
0576: caretColor = (value instanceof Color) ? (Color) value
0577: : SettingsDefaults.defaultCaretColorInsertMode;
0578:
0579: } else {
0580: Object value = Settings
0581: .getValue(
0582: kitClass,
0583: SettingsNames.CARET_COLOR_OVERWRITE_MODE);
0584: caretColor = (value instanceof Color) ? (Color) value
0585: : SettingsDefaults.defaultCaretColorOvwerwriteMode;
0586: }
0587:
0588: if (caretColor != null) {
0589: component.setCaretColor(caretColor);
0590: }
0591: }
0592:
0593: component.setKeymap(Utilities.getKit(component)
0594: .getKeymap());
0595:
0596: // fix for issues 13842, 14003
0597: if (SwingUtilities.isEventDispatchThread()) {
0598: BaseTextUI ui = (BaseTextUI) component.getUI();
0599: ui.updateHeight();
0600: component.repaint();
0601: } else {
0602: SwingUtilities.invokeLater(new Runnable() {
0603: public void run() {
0604: if (component != null) {
0605: BaseTextUI ui = (BaseTextUI) component
0606: .getUI();
0607: ui.updateHeight();
0608: component.repaint();
0609: }
0610: }
0611: });
0612: }
0613: }
0614: }
0615:
0616: coloringMap = null; // reset coloring map so it's lazily rebuilt
0617: fontsInited = false;
0618:
0619: }
0620:
0621: public void stateChanged(final ChangeEvent evt) {
0622: SwingUtilities.invokeLater(new Runnable() {
0623: public void run() {
0624: if (component != null) {
0625: BaseKit kit = Utilities.getKit(component);
0626: if (kit != null) {
0627: boolean selectionVisible = ((Caret) evt
0628: .getSource()).isSelectionVisible();
0629: Action a = kit
0630: .getActionByName(BaseKit.cutAction);
0631: if (a != null) {
0632: a.setEnabled(selectionVisible);
0633: }
0634:
0635: a = kit.getActionByName(BaseKit.copyAction);
0636: if (a != null) {
0637: a.setEnabled(selectionVisible);
0638: }
0639:
0640: a = kit
0641: .getActionByName(BaseKit.removeSelectionAction);
0642: if (a != null) {
0643: a.setEnabled(selectionVisible);
0644: }
0645: }
0646: }
0647: }
0648: });
0649: }
0650:
0651: protected void modelChanged(BaseDocument oldDoc, BaseDocument newDoc) {
0652: if (oldDoc != null) {
0653: // remove all document layers
0654: drawLayerList.remove(oldDoc.getDrawLayerList());
0655: }
0656:
0657: if (newDoc != null) {
0658: settingsChange(null);
0659:
0660: // add all document layers
0661: drawLayerList.add(newDoc.getDrawLayerList());
0662: }
0663:
0664: if (oldDoc != null)
0665: oldDoc.getBookmarks().removeAll();
0666: }
0667:
0668: public void propertyChange(PropertyChangeEvent evt) {
0669: String propName = evt.getPropertyName();
0670:
0671: if ("document".equals(propName)) {
0672: BaseDocument oldDoc = (evt.getOldValue() instanceof BaseDocument) ? (BaseDocument) evt
0673: .getOldValue()
0674: : null;
0675: BaseDocument newDoc = (evt.getNewValue() instanceof BaseDocument) ? (BaseDocument) evt
0676: .getNewValue()
0677: : null;
0678: modelChanged(oldDoc, newDoc);
0679:
0680: } else if ("margin".equals(propName)) { // NOI18N
0681: updateTextMargin();
0682:
0683: } else if ("caret".equals(propName)) { // NOI18N
0684: if (evt.getOldValue() instanceof Caret) {
0685: ((Caret) evt.getOldValue()).removeChangeListener(this );
0686: }
0687: if (evt.getNewValue() instanceof Caret) {
0688: ((Caret) evt.getNewValue()).addChangeListener(this );
0689: }
0690:
0691: } else if ("enabled".equals(propName)) { // NOI18N
0692: if (!component.isEnabled()) {
0693: component.getCaret().setVisible(false);
0694: }
0695: }
0696: }
0697:
0698: protected Map createColoringMap() {
0699: Map cm;
0700:
0701: if (component != null) {
0702: // Use the shared coloring-map to save space and time
0703: cm = getSharedColoringMap(getKitClass());
0704:
0705: } else { // print coloring-map must be created
0706: cm = SettingsUtil.getColoringMap(getKitClass(),
0707: (component == null), true);
0708: // Test if there's a default coloring
0709: if (cm.get(SettingsNames.DEFAULT_COLORING) == null) {
0710: cm.put(SettingsNames.DEFAULT_COLORING,
0711: SettingsDefaults.defaultColoring);
0712: }
0713: }
0714:
0715: return cm;
0716: }
0717:
0718: public int getLineHeight() {
0719: return lineHeight;
0720: }
0721:
0722: public int getLineAscent() {
0723: return lineAscent;
0724: }
0725:
0726: public Map getColoringMap() {
0727: if (coloringMap == null) {
0728: coloringMap = createColoringMap();
0729: }
0730: return coloringMap;
0731: }
0732:
0733: public Coloring getDefaultColoring() {
0734: return (Coloring) getColoringMap().get(
0735: SettingsNames.DEFAULT_COLORING);
0736: }
0737:
0738: public Coloring getColoring(String coloringName) {
0739: return (Coloring) getColoringMap().get(coloringName);
0740: }
0741:
0742: private void updateLineHeight(Graphics g) {
0743: if (debugUpdateLineHeight) {
0744: System.err
0745: .println("EditorUI.updateLineHeight(): Computing lineHeight ...");
0746: }
0747:
0748: Map cm = getColoringMap();
0749: Iterator i = cm.entrySet().iterator();
0750: int maxHeight = 1;
0751: int maxAscent = 0;
0752: while (i.hasNext()) {
0753: Map.Entry me = (Map.Entry) i.next();
0754: String coloringName = (String) me.getKey();
0755: Coloring c = (Coloring) me.getValue();
0756: if (c != null) {
0757: Font font = c.getFont();
0758: if (font != null
0759: && (c.getFontMode() & Coloring.FONT_MODE_APPLY_SIZE) != 0) {
0760: FontMetrics fm = g.getFontMetrics(font);
0761: if (fm != null) {
0762: if (debugUpdateLineHeight) {
0763: if (maxHeight < fm.getHeight()) {
0764: System.err
0765: .println("Updating maxHeight from "
0766: + maxHeight
0767: + " to "
0768: + fm.getHeight()
0769: + ", coloringName="
0770: + coloringName
0771: + ", font=" + font);
0772: }
0773:
0774: if (maxHeight < fm.getHeight()) {
0775: System.err
0776: .println("Updating maxAscent from "
0777: + maxAscent
0778: + " to "
0779: + fm.getAscent()
0780: + ", coloringName="
0781: + coloringName
0782: + ", font=" + font);
0783: }
0784: }
0785:
0786: maxHeight = Math.max(maxHeight, fm.getHeight());
0787: maxAscent = Math.max(maxAscent, fm.getAscent());
0788: }
0789: }
0790: }
0791: }
0792:
0793: // Apply lineHeightCorrection
0794: lineHeight = (int) (maxHeight * lineHeightCorrection);
0795: lineAscent = (int) (maxAscent * lineHeightCorrection);
0796:
0797: }
0798:
0799: /**
0800: * Return whether the fonts are already initialized or not.
0801: */
0802: boolean isFontsInited() {
0803: return fontsInited;
0804: }
0805:
0806: protected void update(Graphics g) {
0807: Class kitClass = Utilities.getKitClass(component);
0808:
0809: // Set the margin
0810: if (kitClass != null) {
0811: Object value = Settings.getValue(kitClass,
0812: SettingsNames.MARGIN);
0813: Insets margin = (value instanceof Insets) ? (Insets) value
0814: : null;
0815: component.setMargin(margin);
0816: }
0817:
0818: // Apply the default coloring to the component
0819: getDefaultColoring().apply(component);
0820:
0821: // Possibly apply the rendering hints
0822: if (renderingHints != null) {
0823: ((Graphics2D) g).setRenderingHints(renderingHints);
0824: }
0825:
0826: Coloring dc = getDefaultColoring();
0827:
0828: // Handle line number fonts and widths
0829: Coloring lnc = (Coloring) getColoringMap().get(
0830: SettingsNames.LINE_NUMBER_COLORING);
0831: if (lnc != null) {
0832: Font lnFont = lnc.getFont();
0833: if (lnFont == null) {
0834: lnFont = dc.getFont();
0835: }
0836: FontMetrics lnFM = g.getFontMetrics(lnFont);
0837: int maxWidth = 1;
0838: char[] digit = new char[1]; // will be used for '0' - '9'
0839: for (int i = 0; i <= 9; i++) {
0840: digit[0] = (char) ('0' + i);
0841: maxWidth = Math.max(maxWidth, lnFM.charsWidth(digit, 0,
0842: 1));
0843: }
0844: lineNumberDigitWidth = maxWidth;
0845: }
0846:
0847: // Update line height
0848: updateLineHeight(g);
0849:
0850: // Update space width of the default coloring's font
0851: FontMetricsCache.Info fmcInfo = FontMetricsCache
0852: .getInfo(getDefaultColoring().getFont());
0853: defaultSpaceWidth = fmcInfo.getSpaceWidth(g);
0854:
0855: // Update total height
0856: if (component != null) {
0857: ((BaseTextUI) component.getUI()).updateHeight();
0858: updateLineNumberWidth(0);
0859: checkLineLimit();
0860: }
0861:
0862: /*
0863: * JDK1.3 patch for the behavior that occurs when the line is wider than
0864: * the screen and the user first clicks End key to go to the end and
0865: * then goes back by (Ctrl+)Left. As the non-simple scrolling mode is
0866: * used in JViewport in 1.3 the line number block appears shifted to the
0867: * right and gets repainted after 300ms which looks ugly. The patch is
0868: * to set the simple scrolling mode into JViewport.
0869: *
0870: * getParentViewport().setScrollMode(0); // 2 stands for
0871: * SIMPLE_SCROLL_MODE
0872: *
0873: */
0874: try {
0875: JViewport vp = getParentViewport();
0876: if (vp != null) {
0877: java.lang.reflect.Method setScrollModeMethod = JViewport.class
0878: .getDeclaredMethod("setScrollMode",
0879: new Class[] { Integer.TYPE }); // NOI18N
0880: setScrollModeMethod.invoke(vp,
0881: new Object[] { new Integer(0) });
0882: }
0883: } catch (Throwable t) {
0884: }
0885:
0886: // Update various sizes
0887: fontsInited = true;
0888:
0889: // update glyph gutter colors and fonts
0890: if (isGlyphGutterVisible()) {
0891: glyphGutter.update();
0892: updateScrollPaneCornerColor();
0893: }
0894:
0895: // FIx of #14295
0896: updateVirtualHeight(0);
0897: }
0898:
0899: public final JTextComponent getComponent() {
0900: return component;
0901: }
0902:
0903: /**
0904: * Get the document to work on. Either component's document or printed
0905: * document is returned. It can return null in case the component's document
0906: * is not instance of BaseDocument.
0907: */
0908: public final BaseDocument getDocument() {
0909: return (component != null) ? Utilities.getDocument(component)
0910: : printDoc;
0911: }
0912:
0913: private Class getKitClass() {
0914: return (component != null) ? Utilities.getKitClass(component)
0915: : ((printDoc != null) ? printDoc.getKitClass() : null);
0916: }
0917:
0918: public Object getProperty(Object key) {
0919: return props.get(key);
0920: }
0921:
0922: public void putProperty(Object key, Object value) {
0923: Object oldValue;
0924: if (value != null) {
0925: oldValue = props.put(key, value);
0926: } else {
0927: oldValue = props.remove(key);
0928: }
0929: firePropertyChange(key.toString(), oldValue, value);
0930: }
0931:
0932: /**
0933: * Create or get extended editor component. The extended component should
0934: * normally be used for editing files instead of just the JEditorPane
0935: * because it offers status bar and possibly other useful components. The
0936: * getExtComponent() should not be used when the JEditorPane is included in
0937: * dialog.
0938: *
0939: * @see #hasExtComponent()
0940: */
0941: public JComponent getExtComponent() {
0942: if (extComponent == null) {
0943: if (component != null) {
0944: setLineNumberEnabled(true); // enable line numbering
0945:
0946: // extComponent will be a panel
0947: extComponent = new JPanel(new BorderLayout());
0948:
0949: // Add the scroll-pane with the component to the center
0950: JScrollPane scroller = new JScrollPane(component);
0951: scroller.getViewport().setMinimumSize(
0952: new Dimension(4, 4));
0953:
0954: // glyph gutter must be created here
0955: glyphGutter = new GlyphGutter(this );
0956: scroller.setRowHeaderView(glyphGutter);
0957:
0958: glyphCorner = new JPanel();
0959: updateScrollPaneCornerColor();
0960: scroller.setCorner(JScrollPane.LOWER_LEFT_CORNER,
0961: glyphCorner);
0962:
0963: extComponent.add(scroller);
0964:
0965: // Install the status-bar panel to the bottom
0966: extComponent.add(getStatusBar().getPanel(),
0967: BorderLayout.SOUTH);
0968: }
0969: }
0970: return extComponent;
0971: }
0972:
0973: /**
0974: * Whether this ui uses extComponent or not.
0975: *
0976: * @see #getExtComponent()
0977: */
0978: public boolean hasExtComponent() {
0979: return (extComponent != null);
0980: }
0981:
0982: public Abbrev getAbbrev() {
0983: if (abbrev == null) {
0984: abbrev = new Abbrev(this , true, true);
0985: }
0986: return abbrev;
0987: }
0988:
0989: public WordMatch getWordMatch() {
0990: if (wordMatch == null) {
0991: wordMatch = new WordMatch(this );
0992: }
0993: return wordMatch;
0994: }
0995:
0996: public StatusBar getStatusBar() {
0997: if (statusBar == null) {
0998: statusBar = new StatusBar(this );
0999: }
1000: return statusBar;
1001: }
1002:
1003: final DrawLayerList getDrawLayerList() {
1004: return drawLayerList;
1005: }
1006:
1007: /** Find the layer with some layer name in the layer hierarchy */
1008: public DrawLayer findLayer(String layerName) {
1009: return drawLayerList.findLayer(layerName);
1010: }
1011:
1012: /**
1013: * Add new layer and use its priority to position it in the chain. If
1014: * there's the layer with same visibility then the inserted layer will be
1015: * placed after it.
1016: *
1017: * @param layer
1018: * layer to insert into the chain
1019: */
1020: public boolean addLayer(DrawLayer layer, int visibility) {
1021: return drawLayerList.add(layer, visibility);
1022: }
1023:
1024: public DrawLayer removeLayer(String layerName) {
1025: return drawLayerList.remove(layerName);
1026: }
1027:
1028: public void repaint(int startY) {
1029: repaint(startY, component.getHeight());
1030: }
1031:
1032: public void repaint(int startY, int height) {
1033: if (height <= 0) {
1034: return;
1035: }
1036: int width = Math.max(component.getWidth(), 0);
1037: startY = Math.max(startY, 0);
1038: component.repaint(0, startY, width, height);
1039: }
1040:
1041: public void repaintOffset(int pos) throws BadLocationException {
1042: repaintBlock(pos, pos);
1043: }
1044:
1045: /** Repaint the block between the given positions. */
1046: public void repaintBlock(int startPos, int endPos)
1047: throws BadLocationException {
1048: BaseTextUI ui = (BaseTextUI) component.getUI();
1049: if (startPos > endPos) { // swap
1050: int tmpPos = startPos;
1051: startPos = endPos;
1052: endPos = tmpPos;
1053: }
1054: try {
1055: int yFrom = ui.getYFromPos(startPos);
1056: int yTo = ui.getYFromPos(endPos);
1057: repaint(yFrom, (yTo - yFrom) + lineHeight);
1058: } catch (BadLocationException e) {
1059: if (Boolean.getBoolean("netbeans.debug.exceptions")) { // NOI18N
1060: e.printStackTrace();
1061: }
1062: }
1063: }
1064:
1065: /** Is the parent of some editor component a viewport */
1066: private JViewport getParentViewport() {
1067: Component pc = component.getParent();
1068: return (pc instanceof JViewport) ? (JViewport) pc : null;
1069: }
1070:
1071: /** Finds the frame - parent of editor component */
1072: public static Frame getParentFrame(Component c) {
1073: do {
1074: c = c.getParent();
1075: if (c instanceof Frame) {
1076: return (Frame) c;
1077: }
1078: } while (c != null);
1079: return null;
1080: }
1081:
1082: /**
1083: * Possibly update virtual width. If the width is really updated, the method
1084: * returns true.
1085: */
1086: public boolean updateVirtualWidth(int width) {
1087: boolean updated = false;
1088: if (width > virtualSize.width) {
1089: int widthInc = componentSizeIncrement.width;
1090: widthInc = (widthInc < 0) ? (lastExtentBounds.width
1091: * (-widthInc) / 100) : widthInc * defaultSpaceWidth;
1092:
1093: virtualSize.width = width + widthInc;
1094: virtualSizeUpdated = true;
1095: updated = true;
1096: }
1097:
1098: return updated;
1099: }
1100:
1101: /**
1102: * Possibly update virtual height. If the height is really updated, the
1103: * method returns true. There is a slight difference against virtual width
1104: * in that if the height is shrinked too much the virtual height is shrinked
1105: * too. 0 can be used to update to the real height.
1106: */
1107: public boolean updateVirtualHeight(int height) {
1108: boolean updated = false;
1109: updateLineNumberWidth(0);
1110:
1111: if (height > 0) {
1112: if (height > virtualSize.height) {
1113: int heightInc = componentSizeIncrement.height;
1114: heightInc = (heightInc < 0) ? (lastExtentBounds.height
1115: * (-heightInc) / 100) : heightInc * lineHeight;
1116:
1117: virtualSize.height = height + heightInc;
1118: virtualSizeUpdated = true;
1119: updated = true;
1120: }
1121:
1122: if (height < virtualSize.height - lastExtentBounds.height) {
1123: virtualSize.height = height;
1124: virtualSizeUpdated = true;
1125: updated = true;
1126: }
1127:
1128: } else { // compute real height - fix of #14295
1129: height = (int) ((TextUI) component.getUI()).getRootView(
1130: component).getPreferredSpan(View.Y_AXIS);
1131: if (height != virtualSize.height) {
1132: virtualSize.height = height;
1133: virtualSizeUpdated = true;
1134: updated = true;
1135: }
1136: }
1137:
1138: return updated;
1139: }
1140:
1141: public boolean isLineNumberEnabled() {
1142: return lineNumberEnabled;
1143: }
1144:
1145: public void setLineNumberEnabled(boolean lineNumberEnabled) {
1146: this .lineNumberEnabled = lineNumberEnabled;
1147: lineNumberVisible = lineNumberEnabled
1148: && lineNumberVisibleSetting;
1149: if (disableLineNumbers)
1150: lineNumberVisible = false;
1151: }
1152:
1153: /**
1154: * Update the width that will be occupied by the line number.
1155: *
1156: * @param maxDigitCount
1157: * maximum digit count that can the line number have. if it's
1158: * lower or equal to zero it will be computed automatically.
1159: */
1160: public void updateLineNumberWidth(int maxDigitCount) {
1161: int oldWidth = lineNumberWidth;
1162:
1163: if (lineNumberVisible) {
1164: try {
1165: if (maxDigitCount <= 0) {
1166: BaseDocument doc = getDocument();
1167: int lineCnt = Utilities.getLineOffset(doc, doc
1168: .getLength()) + 1;
1169: maxDigitCount = Integer.toString(lineCnt).length();
1170: }
1171:
1172: if (maxDigitCount > lineNumberMaxDigitCount) {
1173: lineNumberMaxDigitCount = maxDigitCount;
1174: }
1175:
1176: } catch (BadLocationException e) {
1177: lineNumberMaxDigitCount = 1;
1178: }
1179: lineNumberWidth = lineNumberMaxDigitCount
1180: * lineNumberDigitWidth;
1181: if (lineNumberMargin != null) {
1182: lineNumberWidth += lineNumberMargin.left
1183: + lineNumberMargin.right;
1184: }
1185:
1186: } else {
1187: lineNumberWidth = 0;
1188: }
1189:
1190: updateTextMargin();
1191: if (oldWidth != lineNumberWidth) { // changed
1192: if (component != null) {
1193: component.repaint();
1194: }
1195: }
1196: }
1197:
1198: void checkLineLimit() {
1199: BaseDocument doc = getDocument();
1200: if (doc != null) {
1201: Integer lineLimit = (Integer) doc
1202: .getProperty(BaseDocument.LINE_LIMIT_PROP);
1203: if (lineLimit != null) {
1204: if (component != null) {
1205: // Not using FM cache - could be called too early
1206: FontMetrics fm = component
1207: .getFontMetrics(getDefaultColoring()
1208: .getFont());
1209: if (fm != null) {
1210: int charWidth = fm.stringWidth("A");
1211: updateVirtualWidth(charWidth
1212: * lineLimit.intValue()
1213: + lineNumberWidth);
1214: }
1215: }
1216: }
1217: }
1218: }
1219:
1220: public void updateTextMargin() {
1221: if (!SwingUtilities.isEventDispatchThread()) {
1222: SwingUtilities.invokeLater(new Runnable() {
1223: public void run() {
1224: updateTextMargin();
1225: }
1226: });
1227: }
1228:
1229: Insets orig = textMargin;
1230: Insets cm = (component != null) ? component.getMargin() : null;
1231: int leftWidth = lineNumberWidth + textLeftMarginWidth;
1232: if (cm != null) {
1233: textMargin = new Insets(cm.top, cm.left + leftWidth,
1234: cm.bottom, cm.right);
1235: } else {
1236: textMargin = new Insets(0, leftWidth, 0, 0);
1237: }
1238: if (orig.top != textMargin.top
1239: || orig.bottom != textMargin.bottom) {
1240: ((BaseTextUI) component.getUI()).invalidateStartY();
1241: }
1242: }
1243:
1244: public Rectangle getExtentBounds() {
1245: return getExtentBounds(null);
1246: }
1247:
1248: /**
1249: * Get position of the component extent. The (x, y) are set to (0, 0) if
1250: * there's no viewport or (-x, -y) if there's one.
1251: */
1252: public Rectangle getExtentBounds(Rectangle r) {
1253: if (r == null) {
1254: r = new Rectangle();
1255: }
1256: if (component != null) {
1257: JViewport port = getParentViewport();
1258: if (port != null) {
1259: Point p = port.getViewPosition();
1260: r.width = port.getWidth();
1261: r.height = port.getHeight();
1262: r.x = p.x;
1263: r.y = p.y;
1264: } else { // no viewport
1265: r.setBounds(component.getVisibleRect());
1266: }
1267: }
1268: return r;
1269: }
1270:
1271: /** Get the begining of the area covered by text */
1272: public Insets getTextMargin() {
1273: return textMargin;
1274: }
1275:
1276: public void scrollRectToVisible(final Rectangle r,
1277: final int scrollPolicy) {
1278: Utilities.runInEventDispatchThread(new Runnable() {
1279: public void run() {
1280: scrollRectToVisibleFragile(r, scrollPolicy);
1281: }
1282: });
1283: }
1284:
1285: /** Must be called with EventDispatchThread */
1286: boolean scrollRectToVisibleFragile(Rectangle r, int scrollPolicy) {
1287: Insets margin = getTextMargin();
1288: Rectangle bounds = getExtentBounds();
1289: r = new Rectangle(r); // make copy of orig rect
1290: r.x -= margin.left;
1291: r.y -= margin.top;
1292: bounds.width -= margin.left + margin.right;
1293: bounds.height -= margin.top + margin.bottom;
1294: return scrollRectToVisibleImpl(r, scrollPolicy, bounds);
1295: }
1296:
1297: /**
1298: * Scroll the view so that requested rectangle is best visible. There are
1299: * different scroll policies available.
1300: *
1301: * @return whether the extent has to be scrolled in any direction.
1302: */
1303: private boolean scrollRectToVisibleImpl(Rectangle r,
1304: int scrollPolicy, Rectangle bounds) {
1305: if (bounds.width <= 0 || bounds.height <= 0) {
1306: return false;
1307: }
1308:
1309: // handle find scrolling specifically
1310: if (scrollPolicy == SCROLL_FIND) {
1311: // converted inset
1312: int cnvFI = (scrollFindInsets.left < 0) ? (-bounds.width
1313: * scrollFindInsets.left / 100)
1314: : scrollFindInsets.left * defaultSpaceWidth;
1315:
1316: int nx = Math.max(r.x - cnvFI, 0);
1317:
1318: cnvFI = (scrollFindInsets.right < 0) ? (-bounds.width
1319: * scrollFindInsets.right / 100)
1320: : scrollFindInsets.right * defaultSpaceWidth;
1321:
1322: r.width += (r.x - nx) + cnvFI;
1323: r.x = nx;
1324:
1325: cnvFI = (scrollFindInsets.top < 0) ? (-bounds.height
1326: * scrollFindInsets.top / 100)
1327: : scrollFindInsets.top * lineHeight;
1328:
1329: int ny = Math.max(r.y - cnvFI, 0);
1330:
1331: cnvFI = (scrollFindInsets.bottom < 0) ? (-bounds.height
1332: * scrollFindInsets.bottom / 100)
1333: : scrollFindInsets.bottom * lineHeight;
1334:
1335: r.height += (r.y - ny) + cnvFI;
1336: r.y = ny;
1337:
1338: return scrollRectToVisibleImpl(r, SCROLL_SMALLEST, bounds); // recall
1339: }
1340: // r must be within virtualSize's width
1341: if (r.x + r.width > virtualSize.width) {
1342: r.x = virtualSize.width - r.width;
1343: if (r.x < 0) {
1344: r.x = 0;
1345: r.width = virtualSize.width;
1346: }
1347: return scrollRectToVisibleImpl(r, scrollPolicy, bounds); // recall
1348: }
1349: // r must be within virtualSize's height
1350: if (r.y + r.height > virtualSize.height) {
1351: r.y = virtualSize.height - r.height;
1352: if (r.y < 0) {
1353: r.y = 0;
1354: r.height = virtualSize.height;
1355: }
1356: return scrollRectToVisibleImpl(r, scrollPolicy, bounds);
1357: }
1358:
1359: // if r extends bounds dimension it must be corrected now
1360: if (r.width > bounds.width || r.height > bounds.height) {
1361: Rectangle caretRect = new Rectangle((Rectangle) component
1362: .getCaret());
1363: if (caretRect.x >= r.x
1364: && caretRect.x + caretRect.width <= r.x + r.width
1365: && caretRect.y >= r.y
1366: && caretRect.y + caretRect.height <= r.y + r.height) { // caret
1367: // inside
1368: // requested
1369: // rect
1370: // move scroll rect for best caret visibility
1371: int overX = r.width - bounds.width;
1372: int overY = r.height - bounds.height;
1373: if (overX > 0) {
1374: r.x -= overX * (caretRect.x - r.x) / r.width;
1375: }
1376: if (overY > 0) {
1377: r.y -= overY * (caretRect.y - r.y) / r.height;
1378: }
1379: }
1380: r.height = bounds.height;
1381: r.width = bounds.width; // could be different algorithm
1382: return scrollRectToVisibleImpl(r, scrollPolicy, bounds);
1383: }
1384:
1385: int newX = bounds.x;
1386: int newY = bounds.y;
1387: boolean move = false;
1388: // now the scroll rect is within bounds of the component
1389: // and can have size of the extent at maximum
1390: if (r.x < bounds.x) {
1391: move = true;
1392: switch (scrollPolicy) {
1393: case SCROLL_MOVE:
1394: newX = (scrollJumpInsets.left < 0) ? (bounds.width
1395: * (-scrollJumpInsets.left) / 100)
1396: : scrollJumpInsets.left * defaultSpaceWidth;
1397: newX = Math.min(newX, bounds.x + bounds.width
1398: - (r.x + r.width));
1399: newX = Math.max(r.x - newX, 0); // new bounds.x
1400: break;
1401: case SCROLL_DEFAULT:
1402: case SCROLL_SMALLEST:
1403: default:
1404: newX = r.x;
1405: break;
1406: }
1407: updateVirtualWidth(newX + bounds.width);
1408: } else if (r.x + r.width > bounds.x + bounds.width) {
1409: move = true;
1410: switch (scrollPolicy) {
1411: case SCROLL_SMALLEST:
1412: newX = r.x + r.width - bounds.width;
1413: break;
1414: default:
1415: newX = (scrollJumpInsets.right < 0) ? (bounds.width
1416: * (-scrollJumpInsets.right) / 100)
1417: : scrollJumpInsets.right * defaultSpaceWidth;
1418: newX = Math.min(newX, bounds.width - r.width);
1419: newX = (r.x + r.width) + newX - bounds.width;
1420: break;
1421: }
1422: updateVirtualWidth(newX + bounds.width);
1423: }
1424:
1425: if (r.y < bounds.y) {
1426: move = true;
1427: switch (scrollPolicy) {
1428: case SCROLL_MOVE:
1429: newY = r.y;
1430: newY -= (scrollJumpInsets.top < 0) ? (bounds.height
1431: * (-scrollJumpInsets.top) / 100)
1432: : scrollJumpInsets.top * lineHeight;
1433: break;
1434: case SCROLL_SMALLEST:
1435: newY = r.y;
1436: break;
1437: case SCROLL_DEFAULT:
1438: default:
1439: newY = r.y - (bounds.height - r.height) / 2; // center
1440: break;
1441: }
1442: newY = Math.max(newY, 0);
1443: } else if (r.y + r.height > bounds.y + bounds.height) {
1444: move = true;
1445: switch (scrollPolicy) {
1446: case SCROLL_MOVE:
1447: newY = (r.y + r.height) - bounds.height;
1448: newY += (scrollJumpInsets.bottom < 0) ? (bounds.height
1449: * (-scrollJumpInsets.bottom) / 100)
1450: : scrollJumpInsets.bottom * lineHeight;
1451: break;
1452: case SCROLL_SMALLEST:
1453: newY = (r.y + r.height) - bounds.height;
1454: break;
1455: case SCROLL_DEFAULT:
1456: default:
1457: newY = r.y - (bounds.height - r.height) / 2; // center
1458: break;
1459: }
1460: newY = Math.max(newY, 0);
1461: }
1462:
1463: if (move) {
1464: setExtentPosition(newX, newY);
1465: }
1466: return move;
1467: }
1468:
1469: void setExtentPosition(int x, int y) {
1470: JViewport port = getParentViewport();
1471: if (port != null) {
1472: Point p = new Point(Math.max(x, 0), Math.max(y, 0));
1473: port.setViewPosition(p);
1474: }
1475: }
1476:
1477: public void adjustWindow(int caretPercentFromWindowTop) {
1478: final Rectangle bounds = getExtentBounds();
1479: if (component != null
1480: && (component.getCaret() instanceof Rectangle)) {
1481: Rectangle caretRect = (Rectangle) component.getCaret();
1482: bounds.y = caretRect.y
1483: - (caretPercentFromWindowTop * bounds.height) / 100
1484: + (caretPercentFromWindowTop * lineHeight) / 100;
1485: Utilities.runInEventDispatchThread(new Runnable() {
1486: public void run() {
1487: scrollRectToVisible(bounds, SCROLL_SMALLEST);
1488: }
1489: });
1490: }
1491: }
1492:
1493: /**
1494: * Set the dot according to the currently visible screen window. #param
1495: * percentFromWindowTop percentage giving the distance of the caret from the
1496: * top of the currently visible window.
1497: */
1498: public void adjustCaret(int percentFromWindowTop) {
1499: JTextComponent c = component;
1500: if (c != null) {
1501: Rectangle bounds = getExtentBounds();
1502: bounds.y += (percentFromWindowTop * bounds.height) / 100
1503: - (percentFromWindowTop * lineHeight) / 100;
1504: try {
1505: int offset = ((BaseTextUI) c.getUI())
1506: .getPosFromY(bounds.y);
1507: if (offset >= 0) {
1508: caretSetDot(offset, null, SCROLL_SMALLEST);
1509: }
1510: } catch (BadLocationException e) {
1511: }
1512: }
1513: }
1514:
1515: /**
1516: * Set the position of the caret and scroll the extent if necessary.
1517: *
1518: * @param offset
1519: * position where the caret should be placed
1520: * @param scrollRect
1521: * rectangle that should become visible. It can be null when no
1522: * scrolling should be done.
1523: * @param scrollPolicy
1524: * policy to be used when scrolling.
1525: * @deprecated
1526: */
1527: public void caretSetDot(int offset, Rectangle scrollRect,
1528: int scrollPolicy) {
1529: if (component != null) {
1530: Caret caret = component.getCaret();
1531: if (caret instanceof BaseCaret) {
1532: ((BaseCaret) caret).setDot(offset, scrollRect,
1533: scrollPolicy);
1534: } else {
1535: caret.setDot(offset);
1536: }
1537: }
1538: }
1539:
1540: /**
1541: * Set the position of the caret and scroll the extent if necessary.
1542: *
1543: * @param offset
1544: * position where the caret should be placed
1545: * @param scrollRect
1546: * rectangle that should become visible. It can be null when no
1547: * scrolling should be done.
1548: * @param scrollPolicy
1549: * policy to be used when scrolling.
1550: * @deprecated
1551: */
1552: public void caretMoveDot(int offset, Rectangle scrollRect,
1553: int scrollPolicy) {
1554: if (component != null) {
1555: Caret caret = component.getCaret();
1556: if (caret instanceof BaseCaret) {
1557: ((BaseCaret) caret).moveDot(offset, scrollRect,
1558: scrollPolicy);
1559: } else {
1560: caret.moveDot(offset);
1561: }
1562: }
1563: }
1564:
1565: /**
1566: * This method is called by textui to do the paint. It is forwarded either
1567: * to paint through the image and then copy the image area to the screen or
1568: * to paint directly to this graphics. The real work occurs in draw-engine.
1569: */
1570: protected void paint(Graphics g) {
1571: if (component != null) { // component must be installed
1572: if (!fontsInited && g != null) {
1573: update(g);
1574: getExtentBounds(lastExtentBounds);
1575: }
1576: ((BaseTextUI) component.getUI()).paintRegion(g);
1577: }
1578: }
1579:
1580: /** Returns the line number margin */
1581: public Insets getLineNumberMargin() {
1582: return lineNumberMargin;
1583: }
1584:
1585: /** Returns width of the one digit */
1586: public int getLineNumberDigitWidth() {
1587: return lineNumberDigitWidth;
1588: }
1589:
1590: /** Is glyph gutter created and visible for the document or not */
1591: public boolean isGlyphGutterVisible() {
1592: return glyphGutter != null;
1593: }
1594:
1595: protected void updateScrollPaneCornerColor() {
1596: Coloring lineColoring = (Coloring) getColoringMap().get(
1597: SettingsNames.LINE_NUMBER_COLORING);
1598: Coloring defaultColoring = (Coloring) getDefaultColoring();
1599:
1600: Color backgroundColor;
1601: if (lineColoring.getBackColor() != null)
1602: backgroundColor = lineColoring.getBackColor();
1603: else
1604: backgroundColor = defaultColoring.getBackColor();
1605:
1606: glyphCorner.setBackground(backgroundColor);
1607: }
1608: }
|