/*
* Display.java
*
* Copyright (C) 1998-2004 Peter Graves
* $Id: Display.java,v 1.17 2004/04/01 18:48:42 piso Exp $
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.armedbear.j;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.MouseEvent;
import java.awt.font.GlyphVector;
import java.util.HashMap;
import javax.swing.JComponent;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public final class Display extends JComponent implements Constants,
ActionListener, FocusListener
{
private static final int MAX_LINE_NUMBER_CHARS = 6;
private static final Preferences preferences = Editor.preferences();
private static Font plainFont;
private static Font boldFont;
private static Font italicFont;
private static int charHeight;
private static int charDescent;
private static int charAscent;
private static int charLeading;
private static int charWidth;
private static int spaceWidth; // Width of a space character.
private static int minCharWidth;
private static boolean antialias;
private static boolean underlineBold;
private static boolean emulateBold;
private static int changeMarkWidth;
private static Font gutterFont;
private static int gutterCharAscent;
private static int gutterCharWidth;
private static boolean showGutterBorder = true;
private static int leftMargin;
private final HashMap changedLines = new HashMap();
private final Editor editor;
private Line topLine;
private int pixelsAboveTopLine;
// The offset of the first visible column of the display.
int shift = 0;
// The column containing the caret, relative to the first visible column
// of the display. The absolute column number will be different if we're
// horizontally scrolled (absolute column number = caretCol + shift).
int caretCol;
private char[] textArray;
private int[] formatArray;
private int updateFlag;
// These are set in initializePaint().
private boolean showChangeMarks;
private boolean enableChangeMarks;
private boolean showLineNumbers;
private int gutterWidth;
private Color changeColor;
private Color savedChangeColor;
private Color lineNumberColor;
private int verticalRuleX;
private Color verticalRuleColor;
private Color gutterBorderColor;
private boolean highlightBrackets;
private boolean highlightMatchingBracket;
private Position posBracket;
private Position posMatch;
private Timer timer;
private boolean caretVisible = true;
public Display(Editor editor)
{
this.editor = editor;
if (plainFont == null)
initializeStaticValues();
initialize();
setFocusTraversalKeysEnabled(false);
setToolTipText("");
}
public static void initializeStaticValues()
{
final String fontName = preferences.getStringProperty(Property.FONT_NAME);
final int fontSize = preferences.getIntegerProperty(Property.FONT_SIZE);
plainFont = new Font(fontName, Font.PLAIN, fontSize);
boldFont = new Font(fontName, Font.BOLD, fontSize);
if (preferences.getBooleanProperty(Property.ENABLE_ITALICS))
italicFont = new Font(fontName, Font.ITALIC, fontSize);
else
italicFont = plainFont;
FontMetrics fm = Toolkit.getDefaultToolkit().getFontMetrics(plainFont);
final int plainAscent = fm.getAscent();
final int plainDescent = fm.getDescent();
final int plainLeading = fm.getLeading();
charWidth = fm.charWidth('a');
spaceWidth = fm.charWidth(' ');
minCharWidth = getMinCharWidth(fm);
leftMargin = charWidth;
fm = Toolkit.getDefaultToolkit().getFontMetrics(boldFont);
final int boldAscent = fm.getAscent();
final int boldDescent = fm.getDescent();
final int boldLeading = fm.getLeading();
charAscent = plainAscent;
if (boldAscent > charAscent)
charAscent = boldAscent;
charDescent = plainDescent;
if (boldDescent > charDescent)
charDescent = boldDescent;
// Use no more than 1 pixel of leading.
charLeading = (plainLeading > 0 || boldLeading > 0) ? 1 : 0;
// Apply user-specified adjustments.
final int adjustAscent = preferences.getIntegerProperty(Property.ADJUST_ASCENT);
final int adjustDescent = preferences.getIntegerProperty(Property.ADJUST_DESCENT);
final int adjustLeading = preferences.getIntegerProperty(Property.ADJUST_LEADING);
if (charAscent + adjustAscent >= 0)
charAscent += adjustAscent;
if (charDescent + adjustDescent >= 0)
charDescent += adjustDescent;
if (charLeading + adjustLeading >= 0)
charLeading += adjustLeading;
charHeight = charAscent + charDescent + charLeading;
antialias = preferences.getBooleanProperty(Property.ANTIALIAS);
underlineBold = preferences.getBooleanProperty(Property.UNDERLINE_BOLD);
emulateBold = preferences.getBooleanProperty(Property.EMULATE_BOLD);
String gutterFontName = preferences.getStringProperty(Property.GUTTER_FONT_NAME);
if (gutterFontName == null)
gutterFontName = fontName;
int gutterFontSize = preferences.getIntegerProperty(Property.GUTTER_FONT_SIZE);
if (gutterFontSize == 0)
gutterFontSize = fontSize;
gutterFont = new Font(gutterFontName, Font.PLAIN, gutterFontSize);
fm = Toolkit.getDefaultToolkit().getFontMetrics(gutterFont);
gutterCharAscent = fm.getAscent();
gutterCharWidth = fm.charWidth('0');
changeMarkWidth =
preferences.getIntegerProperty(Property.CHANGE_MARK_WIDTH);
}
public synchronized void initialize()
{
// Explicitly set this to null here. We might be resetting the display.
paintLineImage = null;
// Allocate text and format arrays big enough to handle full screen
// width for narrowest character in font, plus some slack (runs of
// italics tend to get compressed). An extra 25% should be plenty.
int size = Toolkit.getDefaultToolkit().getScreenSize().width * 5 / (minCharWidth * 4);
textArray = new char[size];
formatArray = new int[size];
if (preferences.getBooleanProperty(Property.BLINK_CARET)) {
if (timer == null) {
timer = new Timer(500, this);
addFocusListener(this);
}
} else {
if (timer != null) {
timer.stop();
timer = null;
removeFocusListener(this);
setCaretVisible(true);
}
}
}
protected void finalize() throws Throwable
{
if (timer != null) {
timer.stop();
timer = null;
}
}
private static final int getMinCharWidth(FontMetrics fm)
{
int minWidth = Integer.MAX_VALUE;
int[] widths = fm.getWidths();
int limit = widths.length > 0x7e ? 0x7e : widths.length;
for (int i = limit; i >= 32; i--)
if (widths[i] != 0 && widths[i] < minWidth)
minWidth = widths[i];
return minWidth;
}
public static final int getCharHeight()
{
return charHeight;
}
public static final int getCharWidth()
{
return charWidth;
}
public static final int getImageBorderHeight()
{
return 5;
}
public static final int getImageBorderWidth()
{
return 5;
}
public final Line getTopLine()
{
return topLine;
}
public final int getTopLineNumber()
{
return topLine.lineNumber();
}
public final void setTopLine(Line line)
{
topLine = line;
pixelsAboveTopLine = 0;
}
public final int getPixelsAboveTopLine()
{
return pixelsAboveTopLine;
}
public final void setPixelsAboveTopLine(int pixels)
{
pixelsAboveTopLine = pixels;
}
public final int getShift()
{
return shift;
}
public final void setShift(int n)
{
this.shift = n;
}
public final int getCaretCol()
{
return caretCol;
}
public final void setCaretCol(int n)
{
caretCol = n;
}
public int getAbsoluteCaretCol()
{
return caretCol + shift;
}
public void setAbsoluteCaretCol(int col)
{
caretCol = col - shift;
}
public final void repaintNow()
{
paintImmediately(0, 0, getWidth(), getHeight());
}
public synchronized void repaintChangedLines()
{
if (!Editor.displayReady())
return;
if ((updateFlag & REPAINT) == REPAINT) {
repaint();
return;
}
if (changedLines.isEmpty())
return;
final Buffer buffer = editor.getBuffer();
initializePaint();
try {
buffer.lockRead();
}
catch (InterruptedException e) {
Log.error(e);
return;
}
try {
Graphics2D g2d = (Graphics2D) getGraphics();
if (g2d != null) {
Line line = topLine;
int y = - pixelsAboveTopLine;
final int limit = getHeight();
while (line != null && y < limit) {
if (changedLines.containsKey(line))
paintLine(line, g2d, y);
y += line.getHeight();
line = line.nextVisible();
}
if (y < limit) {
g2d.setColor(editor.getFormatter().getBackgroundColor());
final int height = limit - y;
g2d.fillRect(0, y, getWidth(), height);
if (showLineNumbers)
drawGutterBorder(g2d, y, height);
drawVerticalRule(g2d, y, height);
}
drawCaret(g2d);
}
}
finally {
buffer.unlockRead();
}
changedLines.clear();
}
// Set caret column to be where dot is.
public void moveCaretToDotCol()
{
if (editor.getDot() == null)
return;
int absCol = editor.getDotCol();
if (caretCol != absCol - shift) {
caretCol = absCol - shift;
lineChanged(editor.getDotLine());
}
setUpdateFlag(REFRAME);
}
// Assumes line is after topLine.
private int getY(Line line)
{
int y = - pixelsAboveTopLine;
int limit = getHeight();
for (Line l = topLine; y < limit && l != null && l != line; l = l.nextVisible())
y += l.getHeight();
return y;
}
// Does NOT assume line is after topLine.
private int getAbsoluteY(Line line)
{
int y = 0;
for (Line l = editor.getBuffer().getFirstLine(); l != null && l != line; l = l.nextVisible())
y += l.getHeight();
return y;
}
private void initializePaint()
{
final Buffer buffer = editor.getBuffer();
final Mode mode = editor.getMode();
showChangeMarks = buffer.getBooleanProperty(Property.SHOW_CHANGE_MARKS);
enableChangeMarks = showChangeMarks && !buffer.isNewFile();
if (showChangeMarks) {
changeColor = mode.getColorProperty(Property.COLOR_CHANGE);
if (changeColor == null)
changeColor = DefaultTheme.getColor("change");
savedChangeColor = mode.getColorProperty(Property.COLOR_SAVED_CHANGE);
if (savedChangeColor == null)
savedChangeColor = DefaultTheme.getColor("savedChange");
}
showLineNumbers = buffer.getBooleanProperty(Property.SHOW_LINE_NUMBERS);
gutterWidth = getGutterWidth(showChangeMarks, showLineNumbers);
if (showLineNumbers) {
lineNumberColor = mode.getColorProperty(Property.COLOR_LINE_NUMBER);
if (lineNumberColor == null)
lineNumberColor = DefaultTheme.getColor("lineNumber");
gutterBorderColor = mode.getColorProperty(Property.COLOR_GUTTER_BORDER);
if (gutterBorderColor == null)
gutterBorderColor = DefaultTheme.getColor("gutterBorder");
}
int col = buffer.getIntegerProperty(Property.VERTICAL_RULE);
if (col != 0) {
verticalRuleX = gutterWidth + (col - shift) * charWidth - 1;
verticalRuleColor = mode.getColorProperty(Property.COLOR_VERTICAL_RULE);
if (verticalRuleColor == null)
verticalRuleColor = DefaultTheme.getColor("verticalRule");
} else
verticalRuleX = 0;
highlightBrackets =
buffer.getBooleanProperty(Property.HIGHLIGHT_BRACKETS);
highlightMatchingBracket = highlightBrackets ||
buffer.getBooleanProperty(Property.HIGHLIGHT_MATCHING_BRACKET);
if (highlightMatchingBracket) {
Position oldPosMatch = posMatch;
posBracket = null;
posMatch = null;
if (editor.getDot() != null) {
Position dot = editor.getDotCopy();
char c = dot.getChar();
if (c == '{' || c == '[' || c == '(') {
posBracket = dot;
posMatch = editor.findMatchInternal(dot, 200);
} else if (dot.getOffset() > 0) {
int end = editor.getBuffer().getCol(dot.getLine(),
dot.getLine().length());
if (shift + caretCol <= end) {
dot.skip(-1);
c = dot.getChar();
if (c == '}' || c == ']' || c == ')') {
posBracket = dot;
posMatch = editor.findMatchInternal(dot, 200);
}
}
}
}
if (oldPosMatch != null && oldPosMatch != posMatch)
lineChanged(oldPosMatch.getLine());
if (posMatch != null && posMatch != oldPosMatch)
lineChanged(posMatch.getLine());
if (!highlightBrackets)
posBracket = null;
}
}
private void drawVerticalRule(Graphics g, int y, int height)
{
if (verticalRuleX > gutterWidth) {
g.setColor(verticalRuleColor);
g.drawLine(verticalRuleX, y, verticalRuleX, y + height);
}
}
private Position dragCaretPos;
public void setDragCaretPos(Position pos)
{
if (dragCaretPos != null)
lineChanged(dragCaretPos.getLine());
dragCaretPos = pos;
if (pos != null)
lineChanged(pos.getLine());
}
private int dragCaretCol;
public void setDragCaretCol(int col)
{
dragCaretCol = col;
}
// Called only from synchronized methods.
private void drawDragCaret()
{
if (dragCaretPos == null)
return;
if (topLine == null)
return;
final Line line = dragCaretPos.getLine();
if (line.lineNumber() < topLine.lineNumber())
return;
final int absCol;
if (editor.getBuffer().getBooleanProperty(Property.RESTRICT_CARET))
absCol = editor.getBuffer().getCol(dragCaretPos);
else
absCol = dragCaretCol; // We can go beyond the end of the line.
if (absCol < shift)
return;
final int col = absCol - shift;
formatLine(line, shift, col);
Graphics2D g2d = (Graphics2D) getGraphics();
final int x =
gutterWidth + measureLine(g2d, textArray, col, formatArray);
final int y = getY(line);
g2d.setColor(editor.getFormatter().getCaretColor());
g2d.fillRect(x, y, 1, charAscent + charDescent);
}
// Called only from synchronized methods.
private void drawCaret(Graphics2D g2d)
{
if (dragCaretPos != null) {
drawDragCaret();
return;
}
if (!caretVisible)
return;
if (topLine == null)
return;
if (editor != Editor.currentEditor())
return;
if (editor.getDot() == null)
return;
if (editor.getMark() != null && !editor.getMark().equals(editor.getDot()))
return;
if (caretCol < 0)
return;
if (!editor.getFrame().isActive())
return;
if (editor.getFrame().getFocusedComponent() != this)
return;
final Line dotLine = editor.getDotLine();
if (dotLine instanceof ImageLine)
return;
if (editor.getBuffer().needsRenumbering())
editor.getBuffer().renumber();
if (dotLine.lineNumber() < topLine.lineNumber())
return;
int x;
if (caretCol == 0)
x = gutterWidth;
else if (dotLine.length() == 0)
x = gutterWidth + caretCol * spaceWidth;
else {
formatLine(dotLine, shift, caretCol);
x = gutterWidth + measureLine(g2d, textArray, caretCol, formatArray);
}
if (x > getWidth())
return;
int y = getY(dotLine);
if (y >= getHeight())
return;
g2d.setColor(editor.getFormatter().getCaretColor());
// Caret width is 1 pixel.
g2d.fillRect(x, y, 1, charAscent + charDescent);
}
public synchronized void setCaretVisible(boolean b)
{
caretVisible = b;
if (b && timer != null && timer.isRunning())
timer.restart();
}
private synchronized void blinkCaret()
{
Position dot = editor.getDot();
if (dot != null) {
caretVisible = !caretVisible;
final Line line = dot.getLine();
Runnable r = new Runnable() {
public void run()
{
repaintLine(line);
}
};
SwingUtilities.invokeLater(r);
}
}
private synchronized void repaintLine(Line l)
{
if (!Editor.displayReady())
return;
if ((updateFlag & REPAINT) == REPAINT) {
repaint();
return;
}
final Buffer buffer = editor.getBuffer();
initializePaint();
try {
buffer.lockRead();
}
catch (InterruptedException e) {
Log.error(e);
return;
}
try {
Graphics2D g2d = (Graphics2D) getGraphics();
if (g2d != null) {
Line line = topLine;
int y = - pixelsAboveTopLine;
final int limit = getHeight();
while (line != null && y < limit) {
if (line == l) {
paintLine(line, g2d, y);
break;
}
y += line.getHeight();
line = line.nextVisible();
}
drawCaret(g2d);
}
}
finally {
buffer.unlockRead();
}
}
// Timer event handler.
public void actionPerformed(ActionEvent e)
{
blinkCaret();
}
public synchronized void focusGained(FocusEvent e)
{
if (timer != null)
timer.start();
}
public synchronized void focusLost(FocusEvent e)
{
if (timer != null)
timer.stop();
}
private int formatLine(final Line line, final int begin, final int maxCols)
{
// Avoid getfield overhead.
final int[] fa = formatArray;
final char[] ta = textArray;
final int taLength = ta.length;
Debug.assertTrue(taLength == fa.length);
for (int i = taLength; i-- > 0;) {
ta[i] = ' ';
fa[i] = 0;
}
final int limit = Math.min(maxCols, taLength);
final LineSegmentList segmentList = editor.getFormatter().formatLine(line);
int segmentStart = 0;
int totalChars = 0;
final int size = segmentList.size();
for (int i = 0; i < size; i++) {
final LineSegment segment = segmentList.getSegment(i);
final int segmentLength = segment.length();
if (segmentStart + segmentLength < begin) {
segmentStart += segmentLength;
continue;
}
final int maxAppend = limit - totalChars;
if (segmentStart >= begin) {
if (segmentLength <= maxAppend) {
segment.getChars(0, segmentLength, ta, totalChars);
totalChars += segmentLength;
} else if (maxAppend > 0) {
segment.getChars(0, maxAppend, ta, totalChars);
totalChars += maxAppend;
}
} else {
// segmentStart < begin && segmentStart + segmentLength >= begin
String text = segment.substring(begin - segmentStart);
if (text.length() <= maxAppend) {
text.getChars(0, text.length(), ta, totalChars);
totalChars += text.length();
} else if (maxAppend > 0) {
text.getChars(0, maxAppend, ta, totalChars);
totalChars += maxAppend;
}
}
final int format = segment.getFormat();
int k = segmentStart - begin;
if (k > limit)
break;
for (int j = 0; j < segmentLength; j++, k++) {
if (k >= 0 && k < limit)
fa[k] = format;
}
segmentStart += segmentLength;
}
return totalChars;
}
private Image paintLineImage;
private int paintLineImageWidth;
private int paintLineImageHeight;
private Graphics2D paintLineGraphics;
private final void providePaintLineImage(int width, int height)
{
if (paintLineImage != null &&
paintLineImageWidth == width &&
paintLineImageHeight == height)
return;
// Otherwise...
paintLineImage = createImage(width, height);
paintLineImageWidth = width;
paintLineImageHeight = height;
paintLineGraphics = (Graphics2D) paintLineImage.getGraphics();
if (antialias) {
paintLineGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
}
private final void paintLine(Line line, Graphics2D g2d, int y)
{
if (line instanceof ImageLine)
paintImageLine((ImageLine)line, g2d, y);
else
paintTextLine(line, g2d, y);
}
private synchronized void paintTextLine(Line line, Graphics g, int y)
{
int displayWidth = getWidth();
int maxCols = getMaxCols();
providePaintLineImage(displayWidth, charHeight);
Color backgroundColor;
if (line == getCurrentLine())
backgroundColor = editor.getFormatter().getCurrentLineBackgroundColor();
else
backgroundColor = editor.getFormatter().getBackgroundColor();
drawBackgroundForLine(paintLineGraphics, backgroundColor, line, 0);
int totalChars = formatLine(line, shift, maxCols);
if (editor.getMark() != null) {
// Selection.
Region r = new Region(editor);
handleSelection(r, line, formatArray, paintLineGraphics, 0);
} else if (posMatch != null) {
if (posMatch.getLine() == line)
highlightBracket(posMatch, line, formatArray,
paintLineGraphics, 0);
if (posBracket != null && posBracket.getLine() == line)
highlightBracket(posBracket, line, formatArray,
paintLineGraphics, 0);
}
drawGutterText(paintLineGraphics, line, 0);
if (showLineNumbers && editor.getDot() != null)
drawGutterBorder(paintLineGraphics, 0, line.getHeight());
drawVerticalRule(paintLineGraphics, 0, line.getHeight());
drawText(paintLineGraphics, textArray, totalChars, formatArray, 0);
changedLines.remove(line);
g.drawImage(paintLineImage, 0, y, null);
}
private void paintImageLine(ImageLine imageLine, Graphics g, int y)
{
final int displayWidth = getWidth();
final int lineHeight = imageLine.getHeight();
final int imageWidth = imageLine.getImageWidth();
final int imageHeight = imageLine.getImageHeight();
final int x = gutterWidth - shift * charWidth;
Color backgroundColor;
if (imageLine == getCurrentLine())
backgroundColor = editor.getFormatter().getCurrentLineBackgroundColor();
else
backgroundColor = editor.getFormatter().getBackgroundColor();
g.setColor(backgroundColor);
// Left.
g.fillRect(0, y, x, lineHeight);
// Right.
g.fillRect(x + imageWidth, y, displayWidth - (x + imageWidth), lineHeight);
// Bottom.
if (imageHeight < lineHeight)
g.fillRect(0, y + imageHeight, displayWidth,
lineHeight - imageHeight);
Rectangle rect = imageLine.getRect();
g.drawImage(imageLine.getImage(),
x, y, x + rect.width, y + rect.height,
rect.x, rect.y, rect.x + rect.width, rect.y + rect.height,
null);
}
private void drawBackgroundForLine(Graphics2D g2d, Color backgroundColor,
Line line, int y)
{
if (enableChangeMarks && line.isModified()) {
g2d.setColor(line.isSaved() ? savedChangeColor : changeColor);
g2d.fillRect(0, y, changeMarkWidth, line.getHeight());
g2d.setColor(backgroundColor);
g2d.fillRect(changeMarkWidth, y, getWidth() - changeMarkWidth, line.getHeight());
} else {
g2d.setColor(backgroundColor);
g2d.fillRect(0, y, getWidth(), line.getHeight());
}
}
private void drawGutterText(Graphics g, Line line, int y)
{
int x = showChangeMarks ? changeMarkWidth : 0;
char c = 0;
Annotation annotation = line.getAnnotation();
if (annotation != null)
c = annotation.getGutterChar();
else if (line.next() != null && line.next().isHidden())
c = '+';
if (c != 0) {
char[] chars = new char[1];
chars[0] = c;
g.setColor(editor.getFormatter().getColor(0)); // Default text color.
g.setFont(plainFont);
g.drawChars(chars, 0, 1, x, y + charAscent);
}
x += charWidth;
if (showLineNumbers) {
final String s = String.valueOf(line.lineNumber() + 1);
final int pad = MAX_LINE_NUMBER_CHARS - s.length();
if (pad >= 0) {
x += pad * gutterCharWidth;
g.setColor(lineNumberColor);
g.setFont(gutterFont);
g.drawString(s, x, y + charAscent);
}
}
}
private void drawGutterBorder(Graphics g)
{
int x = getGutterWidth(editor.getBuffer()) - 4;
g.setColor(gutterBorderColor);
g.drawLine(x, 0, x, getHeight());
}
private void drawGutterBorder(Graphics g, int y, int height)
{
int x = getGutterWidth(editor.getBuffer()) - 4;
g.setColor(gutterBorderColor);
g.drawLine(x, y, x, y + height);
}
// Returns width in pixels.
public static final int getGutterWidth(Buffer buffer)
{
return getGutterWidth(buffer.getBooleanProperty(Property.SHOW_CHANGE_MARKS),
buffer.getBooleanProperty(Property.SHOW_LINE_NUMBERS));
}
// Returns width in pixels.
private static final int getGutterWidth(boolean showChangeMarks,
boolean showLineNumbers)
{
int width = charWidth;
if (showChangeMarks)
width += changeMarkWidth;
if (showLineNumbers)
width += MAX_LINE_NUMBER_CHARS * gutterCharWidth + 5;
return width;
}
private void drawText(Graphics2D g2d, char[] textArray, int length,
int[] formatArray, int y)
{
int i = 0;
double x = gutterWidth;
final Formatter formatter = editor.getFormatter();
while (i < length) {
int format = formatArray[i];
int start = i;
while (formatArray[i] == format && i < length)
++i;
int style;
FormatTableEntry entry = formatter.getFormatTableEntry(format);
if (entry != null) {
g2d.setColor(entry.getColor());
style = entry.getStyle();
} else {
// Web mode.
g2d.setColor(formatter.getColor(format));
style = formatter.getStyle(format);
}
Font font;
switch (style) {
case Font.BOLD:
font = boldFont;
break;
case Font.ITALIC:
font = italicFont;
break;
case Font.PLAIN:
default:
font = plainFont;
break;
}
char[] chars = new char[i - start];
System.arraycopy(textArray, start, chars, 0, i - start);
GlyphVector gv = font.createGlyphVector(g2d.getFontRenderContext(), chars);
final double width = gv.getLogicalBounds().getWidth();
if (style == Font.BOLD) {
if (boldFont == plainFont) {
if (underlineBold)
g2d.drawLine((int)x, y + charAscent + 1, (int)(x + width), y + charAscent + 1);
else
g2d.drawGlyphVector(gv, (int)x + 1, y + charAscent);
} else if (emulateBold)
g2d.drawGlyphVector(gv, (float)x + 1, y + charAscent);
}
if (formatter.getUnderline(format))
g2d.drawLine((int)x, y + charAscent + 1, (int)(x + width), y + charAscent + 1);
g2d.drawGlyphVector(gv, (float)x, y + charAscent);
x += width;
}
}
private int measureLine(Graphics2D g2d, char[] textArray, int length, int[] formatArray)
{
if (length == 0)
return 0;
final int limit = Math.min(length, textArray.length);
double totalWidth = 0;
int i = 0;
Formatter formatter = editor.getFormatter();
while (i < limit) {
int format = formatArray[i];
int startCol = i;
while (i < limit && formatArray[i] == format)
++i;
Font font;
switch (formatter.getStyle(format)) {
case Font.BOLD:
font = boldFont;
break;
case Font.ITALIC:
font = italicFont;
break;
case Font.PLAIN:
default:
font = plainFont;
break;
}
char[] chars = new char[i - startCol];
System.arraycopy(textArray, startCol, chars, 0, i - startCol);
GlyphVector gv = font.createGlyphVector(g2d.getFontRenderContext(), chars);
totalWidth += gv.getLogicalBounds().getWidth();
}
return (int) totalWidth;
}
public void paintComponent(Graphics g)
{
final Buffer buffer = editor.getBuffer();
if (!Editor.displayReady()) {
if (buffer != null && buffer.getModeId() == IMAGE_MODE)
g.setColor(ImageBuffer.getDefaultBackgroundColor());
else
g.setColor(editor.getFormatter().getBackgroundColor());
g.fillRect(0, 0, getWidth(), getHeight());
return;
}
try {
buffer.lockRead();
}
catch (InterruptedException e) {
Log.error(e);
return;
}
try {
if (buffer.getModeId() == IMAGE_MODE)
paintImage(g);
else
paintComponentInternal(g);
}
finally {
buffer.unlockRead();
}
}
private void paintImage(Graphics g)
{
ImageBuffer ib = (ImageBuffer) editor.getBuffer();
g.setColor(ib.getBackgroundColor());
g.fillRect(0, 0, getWidth(), getHeight());
Image image = ib.getImage();
if (image != null) {
int imageWidth = image.getWidth(null);
int imageHeight = image.getHeight(null);
int x = 0;
int y = 0;
if (imageWidth > 0 && imageHeight > 0) {
if (imageWidth < getWidth())
x = (getWidth() - imageWidth) / 2;
if (imageHeight < getHeight())
y = (getHeight() - imageHeight) / 2;
}
if (x < getImageBorderWidth())
x = getImageBorderWidth();
if (y < getImageBorderHeight())
y = getImageBorderHeight();
g.drawImage(image, x - shift * charWidth, y - pixelsAboveTopLine, this);
}
}
private synchronized void paintComponentInternal(Graphics g)
{
initializePaint();
Graphics2D g2d = (Graphics2D) g;
if (antialias) {
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
final Rectangle clipBounds = g2d.getClipBounds();
final int displayWidth = getWidth();
final int maxCols = getMaxCols();
// Selection.
final Region r = editor.getMark() != null ? new Region(editor) : null;
// Current line.
final Line currentLine = getCurrentLine();
final Color colorBackground = editor.getFormatter().getBackgroundColor();
int y = - pixelsAboveTopLine;
Line line = topLine;
while (line != null && y + line.getHeight() < clipBounds.y) {
y += line.getHeight();
line = line.nextVisible();
}
final int limit = clipBounds.y + clipBounds.height;
while (line != null && y < limit) {
if (line instanceof ImageLine) {
paintImageLine((ImageLine)line, g2d, y);
} else {
Color backgroundColor;
if (line == currentLine) {
backgroundColor =
editor.getFormatter().getCurrentLineBackgroundColor();
} else
backgroundColor = colorBackground;
drawBackgroundForLine(g2d, backgroundColor, line, y);
int totalChars = formatLine(line, shift, maxCols);
if (r != null)
handleSelection(r, line, formatArray, g2d, y);
else if (posMatch != null) {
if (posMatch.getLine() == line)
highlightBracket(posMatch, line, formatArray, g2d, y);
if (posBracket != null && posBracket.getLine() == line)
highlightBracket(posBracket, line, formatArray, g2d, y);
}
drawGutterText(g2d, line, y);
if (totalChars > 0) {
// Draw vertical rule first so it will be behind the text.
drawVerticalRule(g2d, y, line.getHeight());
drawText(g2d, textArray, totalChars, formatArray, y);
} else
drawVerticalRule(g2d, y, line.getHeight());
changedLines.remove(line);
}
y += line.getHeight();
line = line.nextVisible();
}
if (y < limit) {
g2d.setColor(colorBackground);
g2d.fillRect(0, y, displayWidth, limit - y);
drawVerticalRule(g2d, y, limit - y);
}
drawCaret(g2d);
if (showLineNumbers && editor.getDot() != null)
drawGutterBorder(g2d);
updateFlag &= ~REPAINT;
}
private Line getCurrentLine()
{
if (editor.getDot() != null && editor.getMark() == null)
return editor.getDotLine();
return null;
}
private void handleSelection(Region r, Line line, int[] formatArray,
Graphics2D g2d, int y)
{
if (r == null)
return;
int maxCols = getMaxCols();
int fillHeight = charHeight;
int fillWidth = 0;
int beginCol = 0;
int endCol = 0;
int x = 0;
if (r.isColumnRegion()) {
if (line.lineNumber() >= r.getBeginLineNumber() &&
line.lineNumber() <= r.getEndLineNumber()) {
beginCol = r.getBeginCol() - shift;
endCol = r.getEndCol() - shift;
if (beginCol < 0)
beginCol = 0;
if (endCol < 0)
endCol = 0;
if (endCol > maxCols)
endCol = maxCols;
int x1 = measureLine(g2d, textArray, beginCol, formatArray);
int x2 = measureLine(g2d, textArray, endCol, formatArray);
fillWidth = x2 - x1;
x = gutterWidth + x1;
}
} else if (line == r.getBeginLine()) {
beginCol = r.getBeginCol() - shift;
if (line == r.getEndLine())
endCol = r.getEndCol() - shift;
if (beginCol < 0)
beginCol = 0;
if (endCol < 0)
endCol = 0;
if (endCol > maxCols)
endCol = maxCols;
if (line == r.getEndLine()) {
int x1 = measureLine(g2d, textArray, beginCol, formatArray);
int x2 = measureLine(g2d, textArray, endCol, formatArray);
fillWidth = x2 - x1;
x = gutterWidth + x1;
} else {
fillWidth = getWidth();
x = gutterWidth + measureLine(g2d, textArray, beginCol, formatArray);
}
} else if (line.lineNumber() > r.getBeginLineNumber() &&
line.lineNumber() < r.getEndLineNumber()) {
// Entire line is selected.
fillWidth = getWidth();
x = gutterWidth;
} else if (line == r.getEndLine()) {
// Last line of selection that spans more than one line.
endCol = r.getEndCol() - shift;
if (endCol < 0)
endCol = 0;
if (endCol > maxCols)
endCol = maxCols;
int x1 = measureLine(g2d, textArray, beginCol, formatArray);
int x2 = measureLine(g2d, textArray, endCol, formatArray);
fillWidth = x2 - x1;
x = gutterWidth + x1;
}
if (fillWidth > 0) {
g2d.setColor(editor.getFormatter().getSelectionBackgroundColor());
g2d.fillRect(x, y, fillWidth, charHeight);
}
}
private void highlightBracket(Position pos, Line line, int[] formatArray,
Graphics2D g2d, int y)
{
if (pos == null) {
Debug.bug();
return;
}
if (pos.getLine() != line) {
Debug.bug();
return;
}
if (editor.getFrame().getFocusedComponent() != this)
return;
int beginCol = editor.getBuffer().getCol(pos) - shift;
if (beginCol < 0)
return;
int endCol = beginCol + 1;
int x1 = measureLine(g2d, textArray, beginCol, formatArray);
int x2 = measureLine(g2d, textArray, endCol, formatArray);
int fillWidth = x2 - x1;
int x = gutterWidth + x1;
if (fillWidth > 0) {
g2d.setColor(editor.getFormatter().getMatchingBracketBackgroundColor());
g2d.fillRect(x, y, fillWidth, charHeight);
}
}
// Scroll up in the buffer, moving the content in the window down.
private void scrollUp()
{
if (editor.getModeId() == IMAGE_MODE) {
if (pixelsAboveTopLine >= charHeight) {
pixelsAboveTopLine -= charHeight;
repaint();
}
return;
}
if (topLine != null) {
if (topLine instanceof ImageLine) {
if (pixelsAboveTopLine - charHeight >= 0) {
pixelsAboveTopLine -= charHeight;
scrollPixelsUp(charHeight);
Rectangle r = new Rectangle(0, 0, getWidth(), charHeight);
paintImmediately(r);
return;
}
}
Line line = topLine.previousVisible();
if (line == null)
return;
if (line instanceof ImageLine) {
scrollPixelsUp(charHeight);
topLine = line;
pixelsAboveTopLine = line.getHeight() - charHeight;
editor.update(topLine);
return;
}
scrollPixelsUp(charHeight);
setTopLine(line);
editor.update(topLine);
}
}
// Scroll down in the buffer, moving the content in the window up.
private void scrollDown()
{
if (editor.getModeId() == IMAGE_MODE) {
pixelsAboveTopLine += charHeight;
repaint();
return;
}
if (topLine != null) {
if (topLine instanceof ImageLine) {
if (pixelsAboveTopLine + charHeight < topLine.getHeight()) {
pixelsAboveTopLine += charHeight;
scrollPixelsDown(charHeight);
Rectangle r = new Rectangle();
r.x = 0;
r.y = getHeight() - charHeight;
r.width = getWidth();
r.height = charHeight;
paintImmediately(r);
return;
}
}
Line line = topLine.nextVisible();
if (line != null) {
scrollPixelsDown(charHeight);
setTopLine(line);
Line bottomLine = getBottomLine();
editor.update(bottomLine);
Line nextVisible = bottomLine.nextVisible();
if (nextVisible != null)
editor.update(nextVisible);
}
}
}
// Move content down.
private void scrollPixelsUp(int dy)
{
Point pt1 = editor.getFrame().getLocationOnScreen();
Point pt2 = getLocationOnScreen();
int x = pt2.x - pt1.x;
int y = pt2.y - pt1.y;
editor.getFrame().getGraphics().copyArea(x, y, getWidth(),
getHeight() - dy, 0, dy);
}
// Move content up.
private void scrollPixelsDown(int dy)
{
Point pt1 = editor.getFrame().getLocationOnScreen();
Point pt2 = getLocationOnScreen();
int x = pt2.x - pt1.x;
int y = pt2.y - pt1.y + dy;
editor.getFrame().getGraphics().copyArea(x, y, getWidth(),
getHeight() - dy, 0, - dy);
}
public Line getBottomLine()
{
Line line = topLine;
int y = line.getHeight() - pixelsAboveTopLine;
final int limit = getHeight();
while (true) {
Line next = line.nextVisible();
if (next == null)
break;
y += next.getHeight();
if (y > limit)
break;
line = next;
}
return line;
}
public void up(boolean select)
{
if (editor.getDot() == null)
return;
if (select) {
if (editor.getMark() == null)
editor.addUndo(SimpleEdit.MOVE);
else if (editor.getLastCommand() != COMMAND_UP)
editor.addUndo(SimpleEdit.MOVE);
} else {
if (editor.getMark() != null) {
boolean isLineBlock =
(editor.getDotOffset() == 0 && editor.getMarkOffset() == 0);
editor.addUndo(SimpleEdit.MOVE);
editor.beginningOfBlock();
editor.setGoalColumn(editor.getDotCol());
if (isLineBlock)
return;
} else if (editor.getLastCommand() != COMMAND_UP)
editor.addUndo(SimpleEdit.MOVE);
}
Line dotLine = editor.getDotLine();
Line prevLine = dotLine.previousVisible();
if (prevLine == null)
return;
if (dotLine == topLine) {
// Need to scroll.
windowUp();
}
editor.updateDotLine();
final Buffer buffer = editor.getBuffer();
boolean selectLine = false;
if (select && editor.getMark() == null) {
Position savedDot = null;
if (buffer.getBooleanProperty(Property.AUTO_SELECT_LINE)) {
Line nextLine = editor.getDotLine().next();
if (nextLine != null) {
selectLine = true;
savedDot = new Position(editor.getDot());
editor.getDot().moveTo(nextLine, 0);
caretCol = 0;
}
}
if (selectLine) {
// Make sure absMarkCol will be correct.
int savedShift = shift;
shift = 0;
editor.setMarkAtDot();
shift = savedShift;
} else
editor.setMarkAtDot();
if (savedDot != null)
editor.getSelection().setSavedDot(savedDot);
}
editor.getDot().setLine(prevLine);
editor.updateDotLine();
if (selectLine)
editor.setGoalColumn(0);
editor.moveDotToGoalCol();
}
public void down(boolean select)
{
if (editor.getDot() == null)
return;
if (select) {
if (editor.getMark() == null)
editor.addUndo(SimpleEdit.MOVE);
else if (editor.getLastCommand() != COMMAND_DOWN)
editor.addUndo(SimpleEdit.MOVE);
} else {
if (editor.getMark() != null) {
boolean isLineBlock =
(editor.getDotOffset() == 0 && editor.getMarkOffset() == 0);
editor.addUndo(SimpleEdit.MOVE);
editor.endOfBlock();
editor.setGoalColumn(editor.getDotCol());
if (isLineBlock)
return;
}
else if (editor.getLastCommand() != COMMAND_DOWN)
editor.addUndo(SimpleEdit.MOVE);
}
final Line dotLine = editor.getDotLine();
final Line nextLine = dotLine.nextVisible();
if (nextLine == null)
return;
if (getY(nextLine) + nextLine.getHeight() > getHeight()) {
// Need to scroll.
windowDown();
}
editor.updateDotLine();
final Buffer buffer = editor.getBuffer();
boolean selectLine = false;
if (select && editor.getMark() == null) {
Position savedDot = null;
if (buffer.getBooleanProperty(Property.AUTO_SELECT_LINE)) {
selectLine = true;
savedDot = new Position(editor.getDot());
editor.getDot().setOffset(0);
caretCol = 0;
}
if (selectLine) {
// Make sure absMarkCol will be correct.
int savedShift = shift;
shift = 0;
editor.setMarkAtDot();
shift = savedShift;
} else
editor.setMarkAtDot();
if (savedDot != null)
editor.getSelection().setSavedDot(savedDot);
}
editor.getDot().setLine(nextLine);
editor.updateDotLine();
if (selectLine)
editor.setGoalColumn(0);
editor.moveDotToGoalCol();
}
public void windowUp()
{
if (getHeight() < editor.getBuffer().getDisplayHeight())
scrollUp();
}
public void windowDown()
{
final int totalHeight = editor.getBuffer().getDisplayHeight();
final int windowHeight = getHeight();
if (windowHeight < totalHeight) {
// Add up cumulative height to bottom of window.
int y;
if (topLine != null)
y = editor.getBuffer().getY(topLine) + pixelsAboveTopLine + windowHeight;
else
y = pixelsAboveTopLine + windowHeight;
if (y < totalHeight)
scrollDown();
}
}
public void windowUp(int lines)
{
Line line = topLine;
if (line == null) {
if (editor.getModeId() == IMAGE_MODE) {
pixelsAboveTopLine -= lines * charHeight;
if (pixelsAboveTopLine < 0)
pixelsAboveTopLine = 0;
repaint();
}
return;
}
if (line instanceof ImageLine) {
imageLineWindowUp(lines);
return;
}
int actual = 0;
for (int i = 0; i < lines; i++) {
Line prev = line.previousVisible();
if (prev == null)
break;
if (prev instanceof ImageLine) {
imageLineWindowUp(lines);
return;
}
line = prev;
lineChanged(line);
++actual;
}
scrollPixelsUp(actual * charHeight);
setTopLine(line);
editor.maybeScrollCaret();
editor.updateDisplay();
}
private void imageLineWindowUp(int lines)
{
int oldY = getAbsoluteY(topLine) + pixelsAboveTopLine;
int newY = oldY - lines * charHeight;
if (newY < 0)
newY = 0;
Line line = lineFromAbsoluteY(newY);
if (line != null) {
int y = getAbsoluteY(line);
topLine = line;
pixelsAboveTopLine = newY - y;
setUpdateFlag(REPAINT);
editor.updateDisplay();
}
}
public void windowDown(final int lines)
{
Line top = topLine;
if (top == null) {
if (editor.getModeId() == IMAGE_MODE) {
int windowHeight = getHeight();
int bufferHeight = editor.getBuffer().getDisplayHeight();
pixelsAboveTopLine += lines * charHeight;
if (pixelsAboveTopLine + windowHeight > bufferHeight)
pixelsAboveTopLine = bufferHeight - windowHeight;
repaint();
}
return;
}
if (top instanceof ImageLine) {
imageLineWindowDown(lines);
return;
}
int actual = 0;
Line bottom = getBottomLine();
for (int i = 0; i < lines; i++) {
bottom = bottom.nextVisible();
if (bottom == null)
break;
lineChanged(bottom);
Line next = top.nextVisible();
if (next instanceof ImageLine) {
imageLineWindowDown(lines);
return;
}
if (next == null)
break;
top = next;
++actual;
}
if (bottom != null) {
bottom = bottom.nextVisible();
if (bottom != null)
lineChanged(bottom);
}
scrollPixelsDown(actual * charHeight);
setTopLine(top);
editor.maybeScrollCaret();
editor.updateDisplay();
}
private void imageLineWindowDown(int lines)
{
Line top = topLine;
int oldY = getAbsoluteY(top) + pixelsAboveTopLine;
int newY = oldY + lines * charHeight;
int windowHeight = getHeight();
int bufferHeight = editor.getBuffer().getDisplayHeight();
if (newY + windowHeight > bufferHeight)
newY = bufferHeight - windowHeight;
if (newY == oldY)
return;
Line line = lineFromAbsoluteY(newY);
if (line != null) {
int y = getAbsoluteY(line);
topLine = line;
pixelsAboveTopLine = newY - y;
setUpdateFlag(REPAINT);
editor.updateDisplay();
}
}
public void setUpdateFlag(int mask)
{
updateFlag |= mask;
}
private int reframeParam = 0;
public void setReframe(int n)
{
reframeParam = n;
}
public void reframe()
{
if (!Editor.displayReady())
return;
if (editor.getDot() == null)
return;
if (getHeight() == 0)
return; // Not visible yet.
if (topLine == null || (updateFlag & REFRAME) != 0) {
final Buffer buffer = editor.getBuffer();
try {
buffer.lockRead();
}
catch (InterruptedException e) {
Log.error(e);
return;
}
try {
reframeHorizontally();
reframeVertically();
}
finally {
buffer.unlockRead();
}
updateFlag &= ~REFRAME;
}
}
private void reframeVertically()
{
if (topLine != null && topLine.isHidden()) {
Line prev = topLine.previousVisible();
setTopLine(prev != null ? prev : topLine.nextVisible());
setUpdateFlag(REPAINT);
}
if (topLine == null || mustReframe()) {
Line top = findNewTopLine(editor.getDotLine());
setTopLine(top);
setUpdateFlag(REPAINT);
}
reframeParam = 0;
// Now we need to check to make sure there's not unnecessary
// whitespace at the bottom of the display.
// If we can't go back any further, we have no choice.
if (topLine.previousVisible() == null)
return;
int y = getAbsoluteY(topLine);
int limit =
editor.getBuffer().getDisplayHeight() - getHeight() + charHeight;
if (y > limit) {
// We need to scroll back in the buffer a bit.
Line top = lineFromAbsoluteY(limit);
setTopLine(top);
setUpdateFlag(REPAINT);
reframeParam = 0;
}
}
// Returns true if necessary to reframe vertically.
private boolean mustReframe()
{
final int height = getHeight();
final Line dotLine = editor.getDotLine();
Line line = topLine;
int y = - pixelsAboveTopLine;
while (y < height) {
if (line == dotLine) {
// Whole line must fit.
return y + line.getHeight() > height;
}
Line next = line.nextVisible();
if (next == null)
return true;
y += line.getHeight();
line = next;
}
return true;
}
// Helper for reframeVertically().
private Line findNewTopLine(final Line dotLine)
{
int y;
if (reframeParam == 0)
y = getHeight() / 2; // Default.
else if (reframeParam > 0)
y = (reframeParam - 1) * charHeight;
else // reframeParam < 0
y = getHeight() + reframeParam * charHeight;
Line line = dotLine;
while (true) {
Line prev = line.previousVisible();
if (prev == null)
break;
y -= prev.getHeight();
if (y < 0)
break;
line = prev;
}
return line;
}
private void reframeHorizontally()
{
if (editor.getDot() == null)
return;
if (editor.getDotLine() instanceof ImageLine)
return;
final int absCaretCol = caretCol + shift;
Debug.assertTrue(absCaretCol >= 0);
ensureColumnVisible(editor.getDotLine(), absCaretCol);
caretCol = absCaretCol - shift;
}
public synchronized void ensureColumnVisible(Line line, int absCol)
{
final int oldShift = shift;
if (absCol < 50)
shift = 0;
int col = absCol - shift;
if (col < 0) {
do {
shift -= 8;
if (shift < 0)
shift = 0;
col = absCol - shift;
} while (col < 0);
} else {
if (col > getMaxCols()) {
shift = absCol - getMaxCols();
col = absCol - shift;
Debug.assertTrue(col == getMaxCols());
}
Graphics2D g2d = (Graphics2D) getGraphics();
if (g2d == null) {
Log.error("ensureColumnVisible g2d is null");
return;
}
formatLine(line, shift, col);
int x = measureLine(g2d, textArray, col, formatArray);
final int maxWidth = getWidth() - gutterWidth - charWidth;
while (x > maxWidth) {
shift += 8;
col = absCol - shift;
formatLine(line, shift, col);
x = measureLine(g2d, textArray, col, formatArray);
}
}
if (shift != oldShift)
setUpdateFlag(REPAINT);
}
public void toCenter()
{
Line line = editor.getDotLine();
int limit = getRows() / 2;
for (int i = 0; i < limit; i++) {
Line prev = line.previousVisible();
if (prev == null)
break;
line = prev;
}
setTopLine(line);
setUpdateFlag(REPAINT);
}
public void toTop()
{
Line goal = editor.getDotLine().previousVisible();
if (goal == null)
goal = editor.getDotLine();
if (topLine != goal) {
setTopLine(goal);
setUpdateFlag(REPAINT);
}
}
// Does nothing if entire region is already visible.
public void centerRegion(Line begin, Line end)
{
if (begin == null)
return;
if (end != null) {
if (isLineVisible(begin) && isLineVisible(end))
return; // Entire region is already visible.
}
Line newTopLine = null;
int linesInRegion = 0;
if (end != null) {
for (Line line = begin; line != null && line.isBefore(end); line = line.nextVisible())
++linesInRegion;
} else {
for (Line line = begin; line != null; line = line.nextVisible())
++linesInRegion;
}
int numRows = getRows();
if (numRows > linesInRegion) {
int linesAbove = (numRows - linesInRegion) / 2;
newTopLine = begin;
do {
Line prev = newTopLine.previousVisible();
if (prev != null)
newTopLine = prev;
else
break;
--linesAbove;
}
while (linesAbove > 0);
}
if (newTopLine == null) {
Line prev = begin.previousVisible();
newTopLine = prev != null ? prev : begin;
}
if (topLine != newTopLine) {
setTopLine(newTopLine);
setUpdateFlag(REPAINT);
}
}
private boolean isLineVisible(Line line)
{
return (line.lineNumber() >= getTopLineNumber() &&
line.lineNumber() < getTopLineNumber() + getRows());
}
private final int getMaxCols()
{
// We need some slack here (runs of italics tend to get compressed).
// An extra 25% should be plenty.
return (getWidth() / charWidth) * 5 / 4;
}
public final int getColumns()
{
return (getWidth()-getGutterWidth(editor.getBuffer()))/charWidth - 1;
}
public final int getRows()
{
return getHeight() / charHeight;
}
public void moveCaretToPoint(Point point)
{
final Position dot = editor.getDot();
if (dot == null)
return;
final Line line = lineFromY(point.y);
if (line == null)
return;
if (line != dot.getLine()) {
editor.updateDotLine();
dot.setLine(line);
}
caretCol = Math.max(getColumn(line, point.x), 0);
editor.moveDotToCol(caretCol + shift);
}
public synchronized Position positionFromPoint(Point point, int shift)
{
int savedShift = this.shift;
this.shift = shift;
Position pos = positionFromPoint(point);
this.shift = savedShift;
return pos;
}
public Position positionFromPoint(Point point)
{
return positionFromPoint(point.x, point.y);
}
public Position positionFromPoint(int x, int y)
{
Line line = lineFromY(y);
if (line == null)
return null;
Position pos = new Position(line, 0);
int col = getColumn(line, x);
pos.moveToCol(col + shift, editor.getBuffer().getTabWidth());
return pos;
}
// y is offset from top of window.
public Line lineFromY(int y)
{
if (topLine == null)
return null;
Line line = topLine;
int total = - pixelsAboveTopLine;
final int limit = getHeight();
while (true) {
total += line.getHeight();
if (total > y)
break;
if (total > limit)
break;
Line next = line.nextVisible();
if (next == null)
break;
line = next;
}
return line;
}
// y is absolute offset from start of buffer.
private Line lineFromAbsoluteY(int y)
{
Line line = editor.getBuffer().getFirstLine();
if (line != null) {
int total = 0;
while (true) {
total += line.getHeight();
if (total > y)
break;
Line next = line.nextVisible();
if (next != null)
line = line.nextVisible();
else
break;
}
}
return line;
}
public synchronized int getColumn(Line line, int x)
{
if (line instanceof ImageLine)
return 0;
int maxCols = getMaxCols();
formatLine(line, shift, maxCols);
Graphics2D g2d = (Graphics2D) getGraphics();
int begin = 0;
int end = maxCols;
while (end - begin > 4) {
int pivot = (begin + end) / 2;
int width = measureLine(g2d, textArray, pivot, formatArray);
if (width + gutterWidth > x)
end = pivot + 1;
else
begin = pivot - 1;
}
for (int i = begin; i < end; i++) {
int width = measureLine(g2d, textArray, i, formatArray);
if (width + gutterWidth > x)
return i - 1;
}
return 0; // Shouldn't happen.
}
public boolean isOpaque()
{
return true;
}
public synchronized final void lineChanged(Line line)
{
// Avoid NPE in Hashtable.put().
if (line == null) {
Debug.bug("lineChanged line is null");
return;
}
changedLines.put(line, line);
}
public static void resetDisplay()
{
if (plainFont == null)
return; // Not initialized yet. Nothing to do.
initializeStaticValues();
for (EditorIterator it = new EditorIterator(); it.hasNext();) {
Display display = it.nextEditor().getDisplay();
display.initialize();
display.repaint();
}
}
public static void setRenderingHints(Graphics g)
{
if (antialias) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
}
public String getToolTipText(MouseEvent e)
{
return editor.getMode().getToolTipText(editor, e);
}
}
|