/*
* Copyright (c) 2008-2019 Emmanuel Dupuy.
* This project is distributed under the GPLv3 license.
* This is a Copyleft license that gives the user the right to use,
* copy and modify the code freely for non-commercial purposes.
*/
package org.jd.gui.view.component;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaEditorKit;
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaUI;
import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
import org.fife.ui.rsyntaxtextarea.folding.Fold;
import org.fife.ui.rsyntaxtextarea.folding.FoldManager;
import org.fife.ui.rtextarea.Gutter;
import org.fife.ui.rtextarea.LineNumberList;
import org.fife.ui.rtextarea.RTextArea;
import org.fife.ui.rtextarea.RTextAreaUI;
import javax.swing.*;
import javax.swing.text.EditorKit;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.View;
import java.awt.*;
import java.util.Arrays;
import java.util.Map;
public abstract class CustomLineNumbersPage extends HyperlinkPage {
protected Color errorForeground = Color.RED;
protected boolean showMisalignment = true;
public void setErrorForeground(Color color) {
errorForeground = color;
}
public void setShowMisalignment(boolean b) {
showMisalignment = b;
}
/**
* Map[textarea line number] = original line number
*/
protected int[] lineNumberMap = null;
protected int maxLineNumber = 0;
protected void setMaxLineNumber(int maxLineNumber) {
if (maxLineNumber > 0) {
if (lineNumberMap == null) {
lineNumberMap = new int[maxLineNumber+1];
} else if (lineNumberMap.length <= maxLineNumber) {
int[] tmp = new int[maxLineNumber+1];
System.arraycopy(lineNumberMap, 0, tmp, 0, lineNumberMap.length);
lineNumberMap = tmp;
}
this.maxLineNumber = maxLineNumber;
}
}
protected void initLineNumbers() {
String text = getText();
int len = text.length();
if (len == 0) {
setMaxLineNumber(0);
} else {
int mln = len - text.replace("\n", "").length();
if (text.charAt(len-1) != '\n') {
mln++;
}
setMaxLineNumber(mln);
for (int i=1; i<=maxLineNumber; i++) {
lineNumberMap[i] = i;
}
}
}
protected void setLineNumber(int textAreaLineNumber, int originalLineNumber) {
if (originalLineNumber > 0) {
setMaxLineNumber(textAreaLineNumber);
lineNumberMap[textAreaLineNumber] = originalLineNumber;
}
}
protected void clearLineNumbers() {
if (lineNumberMap != null) {
Arrays.fill(lineNumberMap, 0);
}
}
protected int getMaximumSourceLineNumber() { return maxLineNumber; }
protected int getTextAreaLineNumber(int originalLineNumber) {
int textAreaLineNumber = 1;
int greatestLowerSourceLineNumber = 0;
int i = lineNumberMap.length;
while (i-- > 0) {
int sln = lineNumberMap[i];
if (sln <= originalLineNumber) {
if (greatestLowerSourceLineNumber < sln) {
greatestLowerSourceLineNumber = sln;
textAreaLineNumber = i;
}
}
}
return textAreaLineNumber;
}
@Override protected RSyntaxTextArea newSyntaxTextArea() { return new SourceSyntaxTextArea(); }
public class SourceSyntaxTextArea extends HyperlinkSyntaxTextArea {
@Override protected RTextAreaUI createRTextAreaUI() { return new SourceSyntaxTextAreaUI(this); }
}
/**
* A lot of code to replace the default LineNumberList...
*/
public class SourceSyntaxTextAreaUI extends RSyntaxTextAreaUI {
public SourceSyntaxTextAreaUI(JComponent rSyntaxTextArea) { super(rSyntaxTextArea); }
@Override public EditorKit getEditorKit(JTextComponent tc) { return new SourceSyntaxTextAreaEditorKit(); }
@Override public Rectangle getVisibleEditorRect() { return super.getVisibleEditorRect(); }
}
public class SourceSyntaxTextAreaEditorKit extends RSyntaxTextAreaEditorKit {
@Override public LineNumberList createLineNumberList(RTextArea textArea) { return new SourceLineNumberList(textArea); }
}
/**
* Why 'LineNumberList' is so unexpandable ? Too many private fields & methods and too many package scope.
*/
public class SourceLineNumberList extends LineNumberList {
protected RTextArea rTextArea;
protected Map<?,?> aaHints;
protected Rectangle visibleRect;
protected Insets textAreaInsets;
protected Dimension preferredSize;
public SourceLineNumberList(RTextArea textArea) {
super(textArea, null);
this.rTextArea = textArea;
}
@Override
protected void init() {
super.init();
visibleRect = new Rectangle();
aaHints = RSyntaxUtilities.getDesktopAntiAliasHints();
textAreaInsets = null;
}
/**
* @see org.fife.ui.rtextarea.LineNumberList#paintComponent(java.awt.Graphics)
*/
@Override
protected void paintComponent(Graphics g) {
visibleRect = g.getClipBounds(visibleRect);
if (visibleRect == null) {
visibleRect = getVisibleRect();
}
if (visibleRect == null) {
return;
}
int cellWidth = getPreferredSize().width;
int cellHeight = rTextArea.getLineHeight();
int ascent = rTextArea.getMaxAscent();
FoldManager fm = ((RSyntaxTextArea)rTextArea).getFoldManager();
int RHS_BORDER_WIDTH = getRhsBorderWidth();
FontMetrics metrics = g.getFontMetrics();
int rhs = getWidth() - RHS_BORDER_WIDTH;
if (getParent() instanceof Gutter) { // Should always be true
g.setColor(getParent().getBackground());
} else {
g.setColor(getBackground());
}
g.fillRect(0, visibleRect.y, cellWidth, visibleRect.height);
g.setFont(getFont());
if (aaHints != null) {
((Graphics2D)g).addRenderingHints(aaHints);
}
if (rTextArea.getLineWrap()) {
SourceSyntaxTextAreaUI ui = (SourceSyntaxTextAreaUI)rTextArea.getUI();
View v = ui.getRootView(rTextArea).getView(0);
Element root = rTextArea.getDocument().getDefaultRootElement();
int lineCount = root.getElementCount();
int topPosition = rTextArea.viewToModel(visibleRect.getLocation());
int topLine = root.getElementIndex(topPosition);
Rectangle visibleEditorRect = ui.getVisibleEditorRect();
Rectangle r = LineNumberList.getChildViewBounds(v, topLine, visibleEditorRect);
int y = r.y;
int visibleBottom = visibleRect.y + visibleRect.height;
// Keep painting lines until our y-coordinate is past the visible
// end of the text area.
while (y < visibleBottom) {
r = getChildViewBounds(v, topLine, visibleEditorRect);
// Paint the line number.
paintLineNumber(g, metrics, rhs, y+ascent, topLine + 1);
// The next possible y-coordinate is just after the last line
// painted.
y += r.height;
// Update topLine (we're actually using it for our "current line"
// variable now).
if (fm != null) {
Fold fold = fm.getFoldForLine(topLine);
if ((fold != null) && fold.isCollapsed()) {
topLine += fold.getCollapsedLineCount();
}
}
if (++topLine >= lineCount) {
break;
}
}
} else {
textAreaInsets = rTextArea.getInsets(textAreaInsets);
if (visibleRect.y < textAreaInsets.top) {
visibleRect.height -= (textAreaInsets.top - visibleRect.y);
visibleRect.y = textAreaInsets.top;
}
int topLine = (visibleRect.y - textAreaInsets.top) / cellHeight;
int actualTopY = topLine * cellHeight + textAreaInsets.top;
int y = actualTopY + ascent;
// Get the actual first line to paint, taking into account folding.
topLine += fm.getHiddenLineCountAbove(topLine, true);
// Paint line numbers
g.setColor(getForeground());
int line = topLine + 1;
while ((y < visibleRect.y + visibleRect.height + ascent) && (line <= rTextArea.getLineCount())) {
paintLineNumber(g, metrics, rhs, y, line);
y += cellHeight;
if (fm != null) {
Fold fold = fm.getFoldForLine(line - 1);
// Skip to next line to paint, taking extra care for lines with
// block ends and begins together, e.g. "} else {"
while ((fold != null) && fold.isCollapsed()) {
int hiddenLineCount = fold.getLineCount();
if (hiddenLineCount == 0) {
// Fold parser identified a 0-line fold region... This
// is really a bug, but we'll handle it gracefully.
break;
}
line += hiddenLineCount;
fold = fm.getFoldForLine(line - 1);
}
}
line++;
}
}
}
protected void paintLineNumber(Graphics g, FontMetrics metrics, int x, int y, int lineNumber) {
int originalLineNumber;
if (lineNumberMap != null) {
originalLineNumber = (lineNumber < lineNumberMap.length) ? lineNumberMap[lineNumber] : 0;
} else {
originalLineNumber = lineNumber;
}
if (originalLineNumber != 0) {
String number = Integer.toString(originalLineNumber);
int strWidth = metrics.stringWidth(number);
g.setColor(showMisalignment && (lineNumber != originalLineNumber) ? errorForeground : getForeground());
g.drawString(number, x-strWidth, y);
}
}
public int getRhsBorderWidth() { return ((RSyntaxTextArea)rTextArea).isCodeFoldingEnabled() ? 0 : 4; }
@Override
public Dimension getPreferredSize() {
if (preferredSize == null) {
int lineCount = getMaximumSourceLineNumber();
if (lineCount > 0) {
Font font = getFont();
FontMetrics fontMetrics = getFontMetrics(font);
int count = 1;
while (lineCount >= 10) {
lineCount = lineCount / 10;
count++;
}
int preferredWidth = fontMetrics.charWidth('9') * count + 10;
preferredSize = new Dimension(preferredWidth, 0);
} else {
preferredSize = new Dimension(0, 0);
}
}
return preferredSize;
}
}
}