330 lines
9.2 KiB
Java
330 lines
9.2 KiB
Java
/*
|
|
* 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:
|
|
*<pre>
|
|
* JTextArea ta = new JTextArea();
|
|
* ta.setLineWrap(true);
|
|
* ta.setWrapStyleWord(true);
|
|
* JScrollPane sp = new JScrollPane(ta);
|
|
* sp.setRowHeaderView(new LineNumberView(ta));
|
|
*</pre>
|
|
*
|
|
* @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 <tt>true</tt>, 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 <tt>true</tt>, 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);
|
|
}
|
|
}
|
|
} |