/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package eric.JSprogram; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; import java.awt.Rectangle; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import javax.swing.BorderFactory; import javax.swing.JComponent; import javax.swing.SizeSequence; import javax.swing.UIManager; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import javax.swing.text.BadLocationException; import javax.swing.text.Document; import javax.swing.text.Element; import javax.swing.text.JTextComponent; //import eric.JSprogram.JSConsole; /** * LineNumberView is a simple line-number gutter that works correctly * even when lines are wrapped in the associated text component. This * is meant to be used as the RowHeaderView in a JScrollPane that * contains the associated text component. Example usage: *
 *   JTextArea ta = new JTextArea();
 *   ta.setLineWrap(true);
 *   ta.setWrapStyleWord(true);
 *   JScrollPane sp = new JScrollPane(ta);
 *   sp.setRowHeaderView(new LineNumberView(ta));
 *
* * @author Alan Moore */ public class LineNumberView extends JComponent { // This is for the border to the right of the line numbers. // There's probably a UIDefaults value that could be used for this. private static final Color BORDER_COLOR = Color.GRAY; private final static Color DEFAULT_BACKGROUND = new Color(214, 221, 229); private final static Color DEFAULT_FOREGROUND = Color.black; private static final int WIDTH_TEMPLATE = 99999; private static final int MARGIN = 2; private FontMetrics viewFontMetrics; private int maxNumberWidth; private int componentWidth; private int textTopInset; private int textFontAscent; private int textFontHeight; private JTextComponent text; private SizeSequence sizes; private int startLine = 0; private boolean structureChanged = true; /** * Construct a LineNumberView and attach it to the given text component. * The LineNumberView will listen for certain kinds of events from the * text component and update itself accordingly. * * @param startLine the line that changed, if there's only one * @param structureChanged if true, ignore the line number and * update all the line heights. */ public LineNumberView(JTextComponent text) { if (text == null) { throw new IllegalArgumentException("Text component cannot be null"); } this.text = text; updateCachedMetrics(); UpdateHandler handler = new UpdateHandler(); text.getDocument().addDocumentListener(handler); text.addPropertyChangeListener(handler); text.addComponentListener(handler); // setBorder(BorderFactory.createMatteBorder(0, 0, 0, 1, BORDER_COLOR)); setBorder(BorderFactory.createEmptyBorder()); setBackground(DEFAULT_BACKGROUND); setForeground(DEFAULT_FOREGROUND) ; } /** * Schedule a repaint because one or more line heights may have changed. * * @param startLine the line that changed, if there's only one * @param structureChanged if true, ignore the line number and * update all the line heights. */ private void viewChanged(int startLine, boolean structureChanged) { this.startLine = startLine; this.structureChanged = structureChanged; revalidate(); repaint(); } /** Update the line heights as needed. */ private void updateSizes() { if (startLine < 0) { return; } if (structureChanged) { int count = getAdjustedLineCount(); sizes = new SizeSequence(count); for (int i = 0; i < count; i++) { sizes.setSize(i, getLineHeight(i)); } structureChanged = false; } else { sizes.setSize(startLine, getLineHeight(startLine)); } startLine = -1; } /* Copied from javax.swing.text.PlainDocument */ private int getAdjustedLineCount() { // There is an implicit break being modeled at the end of the // document to deal with boundary conditions at the end. This // is not desired in the line count, so we detect it and remove // its effect if throwing off the count. Element map = text.getDocument().getDefaultRootElement(); int n = map.getElementCount(); Element lastLine = map.getElement(n - 1); if ((lastLine.getEndOffset() - lastLine.getStartOffset()) > 1) { return n; } return n - 1; } /** * Get the height of a line from the JTextComponent. * * @param index the line number * @param the height, in pixels */ private int getLineHeight(int index) { int lastPos = sizes.getPosition(index) + textTopInset; int height = textFontHeight; try { Element map = text.getDocument().getDefaultRootElement(); int lastChar = map.getElement(index).getEndOffset() - 1; Rectangle r = text.modelToView(lastChar); height = (r.y - lastPos) + r.height; } catch (BadLocationException ex) { ex.printStackTrace(); } return height; } /** * Cache some values that are used a lot in painting or size * calculations. Also ensures that the line-number font is not * larger than the text component's font (by point-size, anyway). */ private void updateCachedMetrics() { Font textFont = text.getFont(); FontMetrics fm = getFontMetrics(textFont); textFontHeight = Math.max(fm.getHeight(),10); textFontHeight = JSEditor.TailleTexte; //private donc inaccessible ici, dommage... textFontAscent = fm.getAscent(); textTopInset = text.getInsets().top; Font viewFont = getFont(); boolean changed = false; if (viewFont == null) { viewFont = UIManager.getFont("Label.font"); changed = true; } if (viewFont.getSize() != textFont.getSize()) { viewFont = viewFont.deriveFont(textFont.getSize2D()); changed = true; } viewFontMetrics = getFontMetrics(viewFont); maxNumberWidth = (viewFontMetrics.stringWidth(String.valueOf(WIDTH_TEMPLATE))+10)/2; componentWidth = 2 * MARGIN + maxNumberWidth; if (changed) { super.setFont(viewFont); } } public Dimension getPreferredSize() { return new Dimension(componentWidth, text.getHeight()); } public void setFont(Font font) { super.setFont(font); updateCachedMetrics(); } public void paintComponent(Graphics g) { updateSizes(); Rectangle clip = g.getClipBounds(); g.setColor(getBackground()); g.fillRect(clip.x, clip.y, clip.width, clip.height); g.setColor(getForeground()); int base = clip.y - textTopInset; int first = sizes.getIndex(base); int last = sizes.getIndex(base + clip.height); String text = ""; for (int i = first; i <= last; i++) { text = String.valueOf(i+1); int x = MARGIN + maxNumberWidth - viewFontMetrics.stringWidth(text); int y = sizes.getPosition(i) + textFontAscent + textTopInset; g.drawString(text, x, y); } } class UpdateHandler extends ComponentAdapter implements PropertyChangeListener, DocumentListener { /** * The text component was resized. 'Nuff said. */ public void componentResized(ComponentEvent evt) { viewChanged(0, true); } /** * A bound property was changed on the text component. Properties * like the font, border, and tab size affect the layout of the * whole document, so we invalidate all the line heights here. */ public void propertyChange(PropertyChangeEvent evt) { Object oldValue = evt.getOldValue(); Object newValue = evt.getNewValue(); String propertyName = evt.getPropertyName(); if ("document".equals(propertyName)) { if (oldValue != null && oldValue instanceof Document) { ((Document)oldValue).removeDocumentListener(this); } if (newValue != null && newValue instanceof Document) { ((Document)newValue).addDocumentListener(this); } } updateCachedMetrics(); viewChanged(0, true); } /** * Text was inserted into the document. */ public void insertUpdate(DocumentEvent evt) { update(evt); } /** * Text was removed from the document. */ public void removeUpdate(DocumentEvent evt) { update(evt); } /** * Text attributes were changed. In a source-code editor based on * StyledDocument, attribute changes should be applied automatically * in response to inserts and removals. Since we're already * listening for those, this method should be redundant, but YMMV. */ public void changedUpdate(DocumentEvent evt) { // update(evt); } /** * If the edit was confined to a single line, invalidate that * line's height. Otherwise, invalidate them all. */ private void update(DocumentEvent evt) { Element map = text.getDocument().getDefaultRootElement(); int line = map.getElementIndex(evt.getOffset()); DocumentEvent.ElementChange ec = evt.getChange(map); viewChanged(line, ec != null); } } }