/*
* 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 ro.nextreports.designer.ui.sqleditor;
import java.awt.Color;
import java.awt.Component;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JEditorPane;
import javax.swing.JScrollPane;
import javax.swing.border.Border;
import javax.swing.event.CaretEvent;
import javax.swing.event.CaretListener;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Element;
/**
* Used by <code>EditorScrollPane</code> to display line numbers for a text
* area. This component is capable of displaying line numbers for any
* <code>JEditorPane</code>
* You can also choose the line number font, color.
*
* @author Decebal Suiu
*/
class LineNumberBorder implements Border, CaretListener, DocumentListener,
PropertyChangeListener, ChangeListener {
private static final int MIN_CELL_WIDTH = 24;
private static final int RHS_BORDER_WIDTH = 8;
private static final Color DEFAULT_FOREGROUND = new Color(128, 128, 128);
private static final Color DEFAULT_BORDER_LINE_COLOR = Color.LIGHT_GRAY;
private JEditorPane editorPane;
private JScrollPane scrollPane;
private Font font;
private Color foreground;
private Color background;
private int currentLine; // The last line the caret was on.
private Insets insets;
private int cellHeight; // The height of a "cell" for a line number when word wrap is off.
private int ascent; // The ascent to use when painting line numbers.
private int currentNumLines;
private Color borderLineColor = DEFAULT_BORDER_LINE_COLOR;
/**
* Constructs a new <code>LineNumberBorder</code> using default values for
* line number color (gray).
*
* @param scrollPane The scroll pane using this border as the viewport
* border.
* @param editorPane The text component for which line numbers will be
* displayed.
*/
public LineNumberBorder(JScrollPane scrollPane, JEditorPane editorPane) {
this(scrollPane, editorPane, DEFAULT_FOREGROUND);
}
/**
* Constructs a new <code>LineNumberBorder</code>.
*
* @param scrollPane The scroll pane using this border as the viewport
* border.
* @param editorPane The text component for which line numbers will be
* displayed.
* @param numberColor The color to use for the line numbers. If
* <code>null</code>, a default is used.
*/
public LineNumberBorder(JScrollPane scrollPane, JEditorPane editorPane,
Color numberColor) {
this.editorPane = editorPane;
this.scrollPane = scrollPane;
setForeground(numberColor != null ? numberColor : DEFAULT_FOREGROUND);
Color bg = editorPane.getBackground();
setBackground(bg == null ? Color.WHITE : bg);
editorPane.addCaretListener(this);
editorPane.addPropertyChangeListener(this);
scrollPane.getViewport().addChangeListener(this);
editorPane.getDocument().addDocumentListener(this);
currentLine = 1;
setFont(null); // default font.
insets = new Insets(0, 0, 0, 0);
updateCellHeights();
updateCellWidths();
}
/**
* Called whenever the caret changes position; highlight the correct line
* number.
*
* @param event The caret event.
*/
public void caretUpdate(CaretEvent event) {
int caretPosition = editorPane.getCaretPosition();
int line = editorPane.getDocument().getDefaultRootElement().
getElementIndex(caretPosition) + 1;
if (currentLine != line) {
currentLine = line;
scrollPane.repaint();
}
}
public void changedUpdate(DocumentEvent event) {
}
/**
* Returns the background color of the line number list.
*
* @return The background color.
* @see #setBackground
*/
public Color getBackground() {
return background;
}
/**
* Returns the insets of this border.
*
* @param component This parameter is ignored.
* @return The insets of this border.
*/
public Insets getBorderInsets(Component component) {
return insets;
}
/**
* Returns the font used for the line numbers.
*
* @return The font.
* @see #setFont
*/
public Font getFont() {
return font;
}
/**
* Returns the foreground color of the line number list.
*
* @return The foreground color.
* @see #setForeground
*/
public Color getForeground() {
return foreground;
}
/**
* Returns the color to use to paint line numbers.
*
* @return The color used when painting line numbers.
* @see #setLineNumberColor
*/
public Color getLineNumberColor() {
return getForeground();
}
/**
* Called whenever a character is input (key is typed) in the text
* document we're line-numbering.
*
* @param event The document event.
*/
public void insertUpdate(DocumentEvent event) {
int newNumLines = editorPane.getDocument().getDefaultRootElement().getElementCount();
if (newNumLines > currentNumLines) {
// Adjust the amount of space the line numbers take up,
// if necessary.
if (newNumLines/10 > currentNumLines / 10) {
updateCellWidths();
}
currentNumLines = newNumLines;
}
}
/**
* Returns whether this border is opaque.
*
* @return Whether this border is opaque.
*/
public boolean isBorderOpaque() {
return true;
}
/**
* Paints the line numbers.
*
* @param c The text area.
* @param g The graphics context.
* @param x The x-coordinate of the border.
* @param y The y-coordinate of the border.
* @param width The width of the border.
* @param height The height of the border.
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Element root = editorPane.getDocument().getDefaultRootElement();
Rectangle visibleRect = editorPane.getVisibleRect();
if (visibleRect == null) {
return;
}
// Fill in the background the same color as the text component.
g.setColor(getBackground());
g.fillRect(x, y, insets.left,height);
g.setFont(font);
// Get the first and last lines to paint.
int topLine = visibleRect.y / cellHeight + 1;
int bottomLine = Math.min(topLine + visibleRect.height / cellHeight,
root.getElementCount()) + 1;
// Get where to start painting (top of the row), and where to paint
// the line number (drawString expects y == baseline).
// We need to be "scrolled up" up just enough for the missing part of
// the first line.
int actualTopY = y - (visibleRect.y%cellHeight);
Insets textAreaInsets = editorPane.getInsets();
if (textAreaInsets != null) {
actualTopY += textAreaInsets.top;
}
int y2 = actualTopY + ascent;
// Paint the "border" line.
g.setColor(borderLineColor);
g.drawLine(x + insets.left - 4, 0, x + insets.left - 4, visibleRect.height + 1);
g.setColor(getForeground());
FontMetrics metrics = g.getFontMetrics();
int rhs = x + insets.left - RHS_BORDER_WIDTH;
for (int i= topLine; i < bottomLine; i++) {
String number = Integer.toString(i);
int w = (int) metrics.getStringBounds(number, g).getWidth();
g.drawString(number, rhs - w, y2);
y2 += cellHeight;
}
}
/**
* Called whenever the text area fires a property change event.
*
* @param event The event.
*/
public void propertyChange(PropertyChangeEvent event) {
String name = event.getPropertyName();
// If they changed the background color of the text area.
if (name.equals("background")) {
Color bg = editorPane.getBackground();
setBackground(bg == null ? Color.WHITE : bg);
scrollPane.repaint();
return;
}
// If they change the text area's font, we need to update cell heights
// to match the font's height.
if (name.equals("font")) {
updateCellHeights();
return;
}
// If they change the text area's syntax scheme (i.e., it is an
// SyntaxTextArea), update cell heights.
// TODO
// if (name.equals(SyntaxTextArea.SYNTAX_SCHEME_PROPERTY)) {
// updateCellHeights();
// }
}
/**
* Called whenever a character is removed (ie backspace, delete) in the
* text document we're line-numbering.
*/
public void removeUpdate(DocumentEvent event) {
int newNumLines = editorPane.getDocument().getDefaultRootElement().getElementCount();
if (newNumLines < currentNumLines) { // Used to be <=
// Adjust the amount of space the line numbers take up, if necessary.
if (newNumLines / 10 < currentNumLines / 10) {
updateCellWidths();
}
currentNumLines = newNumLines;
// Need to repaint in case they removed a line by pressing delete.
scrollPane.repaint();
}
}
/**
* Sets the background color of the line number list.
*
* @param background The background color.
* @see #getBackground
*/
public void setBackground(Color background) {
if (background != null && !background.equals(this.background)) {
this.background = background;
}
}
/**
* Sets the font used to render the line numbers.
*
* @param font The <code>java.awt.Font</code> to use to to render the line
* numbers. If <code>null</code>, a 10-point monospaced font
* will be used.
* @see #getFont
*/
public void setFont(Font font) {
if (font == null) {
font = new Font("monospaced", Font.PLAIN, 10);
}
this.font = font;
}
/**
* Sets the foreground color of the line number list.
*
* @param foreground The foreground color.
* @see #getForeground
*/
public void setForeground(Color foreground) {
if (foreground != null && !foreground.equals(this.foreground)) {
this.foreground = foreground;
}
}
/**
* Sets the color to use to paint line numbers.
*
* @param color The color to use when painting line numbers.
* @see #getLineNumberColor
*/
public void setLineNumberColor(Color color) {
setForeground(color);
}
/**
* Messages from the viewport.
*
* @param event The change event.
*/
public void stateChanged(ChangeEvent event) {
scrollPane.repaint();
}
/**
* Returns the color to use to paint border line.
*
* @return The color used when painting border line.
* @see #setBorderLineColor
*/
public Color getBorderLineColor() {
return borderLineColor;
}
/**
* Sets the color of the border line.
*
* @param lineColor The border line color.
* @see #getBorderLineColor
*/
public void setBorderLineColor(Color lineColor) {
this.borderLineColor = lineColor;
}
/**
* Changes the height of the cells in the JList so that they are as tall as
* the height of a line of text in the text area.
*/
private void updateCellHeights() {
FontMetrics fontMetrics = editorPane.getFontMetrics(editorPane.getFont());
cellHeight = fontMetrics.getHeight();
ascent = fontMetrics.getMaxAscent();
}
/**
* Changes the width of the cells in the JList so you can see every digit
* of each.
*/
private void updateCellWidths() {
// Adjust the amount of space the line numbers take up, if necessary.
Font font = getFont();
if (font != null) {
FontMetrics fontMetrics = editorPane.getFontMetrics(font);
int count = 0;
int numLines = editorPane.getDocument().getDefaultRootElement().getElementCount();
while (numLines >= 10) {
numLines = numLines/10;
count++;
}
insets.left = Math.max(fontMetrics.charWidth('9') * (count + 2) + 5,
MIN_CELL_WIDTH);
}
}
protected Rectangle getVisibleEditorRect() {
Rectangle alloc = editorPane.getBounds();
if ((alloc.width > 0) && (alloc.height > 0)) {
alloc.x = alloc.y = 0;
Insets insets = editorPane.getInsets();
alloc.x += insets.left;
alloc.y += insets.top;
alloc.width -= insets.left + insets.right;
alloc.height -= insets.top + insets.bottom;
return alloc;
}
return null;
}
}