/*
* Created on 12.01.2005
*
*/
package util.ui;
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.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JComponent;
import javax.swing.JTextArea;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.DefaultStyledDocument;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
/**
* Adds Line-Numbes to JTextAreas
*
* Example:
* <code>
* JTextArea area = new JTextArea();
* JScrollPane scrollPane = new JScrollPane(area);
* LineNumberHeader header = new LineNumberHeader(area);
* scrollPane.setRowHeaderView(header);
* </code>
*
* @author Christoph Mohr
*
*/
public class LineNumberHeader extends JComponent {
private int maxLines = 0;
private int borderWidth = 2;
private boolean borderLine = false;
private JTextComponent textComponent;
private FontMetrics metrics;
/**
* Create the LineNumberHeader
*
* @param textArea TextArea to use
* @param borderWidth Width of the Border
* @param borderLine Draw Border-Lines
*/
public LineNumberHeader(JTextComponent textArea, int borderWidth, boolean borderLine) {
this(textArea);
this.borderWidth = Math.max(0, borderWidth);
this.borderLine = borderLine;
}
/**
* Create the LineNumberHeader
*
* @param textArea TextArea to use
*/
public LineNumberHeader(JTextComponent textArea) {
this.textComponent = textArea;
register();
}
private void register() {
this.textComponent.getDocument().addDocumentListener(new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
setLines();
}
public void removeUpdate(DocumentEvent e) {
setLines();
}
public void changedUpdate(DocumentEvent e) {
setLines();
}
});
this.textComponent.addPropertyChangeListener("font", new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
setMetrics();
}
});
// Initiale Werte setzen
setMetrics();
setLines();
}
/**
* Set local FontMetrics from the <code>JTextArea</code>
* Calls revalidate() and repaint()
*/
private void setMetrics() {
Font refFont = null;
Document doc = this.textComponent.getDocument();
if(doc instanceof DefaultStyledDocument) {
DefaultStyledDocument myDoc = (DefaultStyledDocument)doc;
refFont = myDoc.getFont(myDoc.getDefaultRootElement().getElement(0)
.getAttributes());
} else {
refFont = this.textComponent.getFont();
}
metrics = this.textComponent.getFontMetrics(refFont);
revalidate();
repaint();
}
/**
* Setzt lokal die max. Zeilenzahl.
* does revalidate() and repaint(), if necessary
*/
private void setLines() {
int newLines;
if (textComponent instanceof JTextArea) {
newLines = ((JTextArea)textComponent).getLineCount();
} else {
newLines = textComponent.getDocument().getDefaultRootElement().getElementCount();
}
if (newLines != this.maxLines) {
if (newLines / 10 != this.maxLines / 10) {
// Neuer 10er Schritt, Platz schaffen bzw. freigeben
revalidate();
}
this.maxLines = newLines;
repaint(this.getVisibleRect());
}
}
public Dimension getPreferredSize() {
// Breite des Gesamtstrings ermitteln
// Also: Maximale-Zeilenzahl als String, dessen Stringbreite
final int w = metrics.stringWidth(Integer.toString(maxLines));
// hight of a text line
final int h = metrics.getHeight();
// Breite: 2 * Randbreite + max. Stringbreite
// height: max line number * hight of a row
return new Dimension(w + 2 * this.borderWidth, maxLines * h + this.textComponent.getMargin().top
+ this.textComponent.getMargin().bottom);
}
public Dimension getMinimumSize() {
return getPreferredSize();
}
public void paint(Graphics g) {
super.paint(g);
// Font setzen
g.setFont(metrics.getFont());
g.setColor(Color.DARK_GRAY);
// Maximale Stringbreite (wie in getPrefSize()
final int w = metrics.stringWidth(Integer.toString(maxLines));
// font height (wie in getPrefSize()
final int h = metrics.getHeight();
// Verschiebung nach links um Gesamtbreite (String) + ein Rand
int x = getWidth() - this.borderWidth - w;
// y = Absoluter Startpunkt für erste Textzeile
int y = this.textComponent.getMargin().top + metrics.getAscent();
Rectangle rect = g.getClipBounds();
// Gesucht: 1. Absolute Textzeile die Sichtbar ist!
if (rect.y > y) {
y = (rect.y / h) * h + y;
}
int start = y / h;
if (start == 0) {
start = 1;
}
int end = Math.min(maxLines, rect.height / h + start);
for (int i = start; i <= end; i++) {
// Die Zeilennummer
final String s = Integer.toString(i);
// Breite des aktuell zu zeichnenden Strings
final int sw = metrics.stringWidth(s);
// String zeichnen
g.drawString(s, x + w - sw, y);
// y-Position setzen
y += h;
}
if (borderLine) {
// Abgrenzungslinie zeichnen
g.drawLine(getWidth() - 1, rect.y, getWidth() - 1, rect.y + rect.height);
}
}
}