📄 textarea.java
字号:
package ergo.ui;
// $Id: TextArea.java,v 1.3 1999/08/13 01:20:11 sigue Exp $
/*
* Copyright (C) 1999 Carl L. Gay and Antranig M. Basman.
* See the file copyright.txt, distributed with this software,
* for further information.
*/
/*
* To do list:
*
* - Make thread safe.
* - Finish implementing horiz scrolling.
* - Implement replaceText(), setText(), ...
* - appendText() shouldn't always end with an implicit \n.
* - Implement Copy to clipboard.
* - Resizing the TextCanvas needs to update the number of historyLines
* correctly. Should start a background thread to recalc it, in case
* the history is large. Will have to do some interesting locking
* with anything that may modify the history.
*
*/
import ergo.util.Debug;
import java.util.Vector;
import java.awt.*;
import java.awt.event.AdjustmentListener;
import java.awt.event.AdjustmentEvent;
import java.awt.event.MouseListener;
/*
* Helper classes
*/
/*
* A HistoryElement represents one line of text delimited by \n. (The
* \n is not actually stored.) It may eventually contain a style-map,
* i.e. ranges of the line that have a particular text style
* (font/face/size/color), and perhaps also information about
* hyperlinks. (Hey, I can dream can't I?)
*
* A HistoryElement may take up more than one physical line on the screen
* if it is too long and wraps around. That's where all the complications
* come in...
*/
class HistoryElement {
// The text of the element are stored in a char array since we will have
// to display parts of the string at a time and the Graphics class has
// no drawString method that takes indices into the string, thus making
// it necessary to call substring, which would be unacceptable.
private char[] text;
// lines stores indices into text (above) where the physical lines
// that correspond to the history element begin and end. i.e., if
// one history element wraps so it's displayed on two physical
// screen lines then lines contains 1 index which points to the
// beginning of the second line. Since the first line always begins
// at 0 it is not stored. (Want to save space here since there may
// be thousands of these... These being shorts means a history element
// can't be longer than 32768 chars.)
private short[] lines = null;
// Might want to store the line lengths here, to be able to better deal
// with whitespace at the beginning of wrapped lines. Otherwise,
// wrapping nicely (i.e., no extra space at the beginning or end of
// lines) would be very expensive.
// e.g., private short[] lengths = null;
//StyleVector stylemap;
// For now a history element can have a single color.
public Color color = Color.black;
HistoryElement (String s, Color c) {
text = s.toCharArray();
color = c;
}
public char[] getChars () { return text; }
public short[] getLines () { return lines; }
public void setLines (short[] lines) { this.lines = lines; }
public int getLineOffset (int index) {
if (index == 0)
return 0;
else
return lines[index - 1];
}
public int getLineLength (int index) {
if (index == 0) {
if (lines == null || lines.length == 0)
return text.length;
else
return lines[0];
}
else {
if (index < lines.length)
return lines[index] - lines[index - 1];
else
return text.length - lines[index - 1];
}
}
}
/*
* The History class represents all output displayed in the TextCanvas.
* It is basically just a glorified Vector.
*/
class History {
private Vector v;
// Width in pixels of the widest line output to the canvas.
// Of use to horiz scrollbar in TextArea
private int widestLine = 0;
// Number of physical lines (not history elements) in this history.
private int numLines = 0;
public History (int initialSize, int sizeIncrement) {
v = new Vector(initialSize, sizeIncrement);
}
public HistoryElement elementAt (int index) {
return (HistoryElement) v.elementAt(index);
}
public int size () { return v.size(); }
// Return the index of the first element in the history, or -1 if
// there are no elements yet. (This will be used for circular buffers.)
public int startIndex () {
if (v.size() == 0)
return 0;
else
return -1;
}
// Return the index of the last element in the history, or -1 if
// there are no elements yet. (This will be used for circular buffers.)
public int endIndex () {
return v.size() - 1;
}
// Return the index of where the element after i would be stored.
// (This will be used for circular buffers.)
public int nextIndex (int i) {
return i + 1;
}
// Return the index of where the element before i would be stored.
// (This will be used for circular buffers.)
public int prevIndex (int i) {
return i - 1;
}
// Add an element to the buffer, returning its index.
// Unfortunately this duplicates some logic in nextIndex (above).
public int addElement (HistoryElement elem) {
int index = nextIndex(endIndex());
v.addElement(elem);
return index;
}
// Invalidate all history elements so their line breaks need to be
// recomputed.
public void invalidate () {
for (int i = 0; i < v.size(); i++)
elementAt(i).setLines(null);
}
// Write it to a stream. It's the responsibility of the caller
// to close the stream. (They might want to add something after.)
public void save (java.io.PrintWriter outstream) {
for (int i = 0; i < size(); i++) {
outstream.print(elementAt(i).getChars());
outstream.println();
}
}
}
/*
* A TextCanvas object is a multi-line area that displays text with
* optional line wrapping. Any variables that refer to "lines" refer
* to the virtual lines visible to the user. These are distinct from
* history elements. If a history element is longer than the number of
* columns in the canvas then it wraps to create some number of virtual
* lines.
*
* public appendLine(String)
* - Appends the given string to the output history of the canvas
* and, if the canvas has not been scrolled up, displays the string.
* public moveViewportUp(int numLines)
* - Scrolls backward in the history the specified number of lines.
* public moveViewportDown(int numLines)
* - Scrolls forward in the history the specified number of lines.
* public scrollToEnd()
* public scrollToBeginning()
* public clearHistory()
* - Erase all history elements and clear the canvas.
*/
class TextCanvas extends Canvas {
private Insets insets = new Insets(1, 3, 1, 3); // top, left, bot, right
private boolean border = false;
private int rows;
private int cols;
private int width = -1;
private int height = -1;
private int insideWidth = -1;
private int maxLineWidth = 0; // used for horiz scrolling.
private Image offscreen = null;
private Graphics offg = null;
private boolean wrap = true; // Wrap long lines to next line?
private History history;
private int histSize;
private int histMax;
private int tabWidth = 60; // tab width in pixels
// The number of lines in the history. Note that if some lines wrap
// then this is not the number of history elements!
private int historyLines = 0;
private Font font = new Font("Monospaced", Font.PLAIN, 12);
// The following store the history element at the bottom of the
// canvas. hbot is an index into the history, and lbot is an index
// into the HistoryElement "lines" array. bottomLine tells what
// virtual line is at the bottom of the canvas. i.e., what line
// corresponds to hbot,lbot. It is useful for implementing a scrollbar.
private int hbot;
private int lbot;
private int bottomLine;
// baselines of top and bottom lines, respectively.
private int ymin = -1;
private int ymax = -1;
public TextCanvas (String text, int rows, int columns,
int histSize, int histMax, boolean wrap) {
super();
this.rows = rows;
this.cols = columns;
this.histSize = histSize;
this.histMax = histMax;
this.wrap = wrap;
clearHistory();
if (text != null)
appendText(text);
}
private boolean isInitialized () {
return (width != -1);
}
public int getRows () { return rows; }
public int getColumns () { return cols; }
public Insets getInsets () { return insets; }
public void setInsets (Insets i) { if (i != null) insets = i; }
public void setBorder (boolean b) { border = b; }
public void setFont (Font f) {
if (f != null) {
super.setFont(f);
font = f;
if (offg != null)
offg.setFont(f);
}
else {
Debug.println("Error: Attempt to set font to null (ignored).");
Debug.backtrace();
}
}
// Return the width of the widest line drawn on the canvas so far.
// Note that some history elements may not ever have been rendered
// after the canvas was resized, so they won't be taken into account.
// This may be useful for horizontal scrolling.
public int getMaxLineWidth () { return maxLineWidth; }
public int getHistoryLines () { return historyLines; }
public int getBottomDisplayedLine () { return Math.max(0, bottomLine); }
private Dimension getMySize (int rows, int cols) {
FontMetrics m = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
int lineHeight = m.getHeight();
int charWidth = m.charWidth('m');
Insets insets = getInsets();
return new Dimension(insets.top + insets.bottom + charWidth * cols,
insets.left + insets.right + lineHeight * rows);
}
// Preferred size = rows x columns
public Dimension getPreferredSize () {
Dimension d = getMySize(rows, cols);
//Debug.println("TextCanvas.getPreferredSize returning " + d);
return d;
}
// Minimum size = 1 row x 10 columns
public Dimension getMinimumSize () { return getMySize(3, 10); }
public void clearHistory () {
history = new History(histSize, histSize * 2);
if (offg != null)
clearCanvas(offg);
hbot = lbot = historyLines = 0;
bottomLine = -1;
}
// Recompute the number of lines used by the given element, based
// on the inside width of the canvas. Update maxLineWidth while
// we're at it.
private short[] computeLines (HistoryElement e, FontMetrics m) {
short[] lines = e.getLines();
if (lines == null) {
if (m == null)
m = Toolkit.getDefaultToolkit().getFontMetrics(getFont());
int[] widths = m.getWidths();
Vector v = new Vector(2, 2);
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -