
jive3.JTextEditor Maven / Gradle / Ivy
The newest version!
package jive3;
/*
* JResEditor.java
*
* Simple multiline text editor. (Support styled and colored text)
* JL Pons
*
*/
import javax.swing.*;
import java.awt.*;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.StringSelection;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
/**
* Text Editor class
*/
public final class JTextEditor extends JComponent implements FocusListener,MouseListener,MouseMotionListener,MouseWheelListener,KeyListener {
/**
* The editor content
*/
final class EditorContent {
private static final int SPARE_CAPACITY = 128;
private char[] buffer;
private int[] textInfo;
private int used;
public EditorContent() {
buffer = new char[SPARE_CAPACITY];
textInfo = new int[2*SPARE_CAPACITY];
}
public final int length() {
return used;
}
public final int capacity() {
return buffer.length;
}
public final int charAt(int index) {
if (index >= 0 && index < length())
return buffer[index];
else
return -1;
}
public final boolean isNewLine(int index) {
return buffer[index]=='\n';
}
public String subString(int begin, int length) {
return new String(buffer,begin,length);
}
public void setCharAt(int index, char c) {
buffer[index] = c;
}
public void ensureCapacity(int minimumCapacity) {
if (buffer.length < minimumCapacity) {
int newCapacity = buffer.length * 2 + 2;
if (newCapacity < minimumCapacity)
newCapacity = minimumCapacity;
char newBuffer[] = new char[newCapacity];
int newTextInfo[] = new int[2*newCapacity];
System.arraycopy(buffer, 0, newBuffer, 0, used);
System.arraycopy(textInfo, 0, newTextInfo, 0, 2*used);
buffer = newBuffer;
textInfo = newTextInfo;
}
}
public void setText(String s) {
used = 0;
append(s);
}
public EditorContent append(String s) {
if (s == null)
s = "null";
int addedLength = s.length();
int combinedLength = used + addedLength;
ensureCapacity(combinedLength);
s.getChars(0, addedLength, buffer, used);
for(int i=0;i buffer.length)
ensureCapacity(used + 1);
buffer[used] = c;
textInfo[2*used] = defaultColor.getRGB();
textInfo[2*used+1] = 0;
used++;
return this;
}
public final String toString() {
return new String(buffer, 0, used);
}
public void clearStyleAndColor() {
int rgb = defaultColor.getRGB();
for(int i=0;i=length) stop = length;
if(start>=length) start = length-1;
int rgb = (c.getRed()&0xFF)<<16 |
(c.getGreen()&0xFF)<<8 |
c.getBlue();
for(int i=start;i=length) stop = length;
if(start>=length) start = length-1;
for(int i=start;i=length()) {
append(c);
} else {
int over = length() - idx;
System.arraycopy(buffer,idx,buffer,idx+1,over);
System.arraycopy(textInfo,2*idx,textInfo,2*idx+2,2*over);
buffer[idx] = c;
textInfo[2*idx] = defaultColor.getRGB();
textInfo[2*idx+1] = 0;
used++;
}
}
public void insert(String s,int idx) {
ensureCapacity(used+s.length());
if(idx>=length()) {
append(s);
} else {
int over = length() - idx;
System.arraycopy(buffer,idx,buffer,idx+s.length(),over);
System.arraycopy(textInfo,2*idx,textInfo,2*(idx+s.length()),2*over);
for(int i=0;i undoBuffer;
private int undoPos = 0;
private boolean isEditable;
private ArrayList docListeners;
private Dimension lastSize = null;
private JViewport parentViewport = null;
private String lastSearch = null;
/**
* Construct a JTextEditor
*/
public JTextEditor() {
text = new EditorContent();
initializeDefault();
setOpaque(true);
setToolTipText("");
setCursor(new Cursor(Cursor.TEXT_CURSOR));
textCursorWidth = 2;
setToolTipText(null);
clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
undoBuffer = new ArrayList();
isEditable = true;
docListeners = new ArrayList();
setFocusable(true);
addMouseListener(this);
addMouseMotionListener(this);
addKeyListener(this);
addFocusListener(this);
}
/**
* Sets the text
* @param s Text
*/
public void setText(String s) {
text.setText(s);
resetSelection();
cursorPos = 0;
undoBuffer.clear();
undoPos = 0;
updateScroll(true);
}
/**
* Returns current text
*/
public String getText() {
return text.toString();
}
/**
* Sets the foreground color of the specified area
* @param c Foreground color
* @param start Area start index
* @param lgth Area length
*/
public void setForeground(Color c,int start,int lgth) {
text.setForeground(c,start,lgth);
}
/**
* Sets the style color of the specified area
* @param style Style (Font.PLAIN,Font.BOLD,Font.ITALIC)
* @param start Area start index
* @param lgth Area length
*/
public void setStyle(int style,int start,int lgth) {
text.setStyle(style, start, lgth);
}
/**
* Sets whether the text area is editable
*/
public void setEditable(boolean editable) {
isEditable = editable;
repaint();
}
/**
* Returns true if the text area is editable
*/
public boolean isEditable() {
return isEditable;
}
/**
* Add documents listener
*/
public void addActionListener(ActionListener l) {
docListeners.add(l);
}
public void removeActionListener(ActionListener l) {
docListeners.remove(l);
}
/**
* Sets the default foreground color
* @param f Foreground color
*/
public void setDefaultForegroundColor(Color f) {
defaultColor = f;
}
/**
* Reset style and color to default
*/
public void clearStyleAndColor() {
text.clearStyleAndColor();
}
/**
* Search text
* @param toSearch String to search
*/
public void searchText(String toSearch,boolean matchCase) {
searchText(toSearch, matchCase, cursorPos);
}
private void searchText(String toSearch,boolean matchCase,int from) {
String text = getText();
if(!matchCase) {
toSearch = toSearch.toLowerCase();
text = text.toLowerCase();
}
int i = text.indexOf(toSearch,from);
if(i!=-1) {
lastSearch = toSearch;
selStart = i;
selEnd = i+toSearch.length();
cursorPos = i;
lastCursorPos = i;
fireUpdate();
repaint();
scrollToVisible();
} else {
lastSearch = null;
int ok = JOptionPane.showConfirmDialog(this,"End of document reached, restart from beginning ?","Search",JOptionPane.YES_NO_OPTION);
if(ok == JOptionPane.YES_OPTION) {
searchText(toSearch,matchCase,0);
}
}
}
/**
* Search next
*/
public void searchNext(boolean matchCase) {
if(lastSearch!=null) {
searchText(lastSearch,matchCase,cursorPos+1);
}
}
/**
* Sets the scrollPane parent when the component is used inside a scrollPane
* @param parent ScrollPane parent
*/
public void setScrollPane(JScrollPane parent) {
// Disable arrow key
parent.getActionMap().put("unitScrollRight", new AbstractAction(){
public void actionPerformed(ActionEvent e) {}});
parent.getActionMap().put("unitScrollDown", new AbstractAction(){
public void actionPerformed(ActionEvent e) {}});
parent.getActionMap().put("unitScrollLeft", new AbstractAction(){
public void actionPerformed(ActionEvent e) {}});
parent.getActionMap().put("unitScrollUp", new AbstractAction(){
public void actionPerformed(ActionEvent e) {}});
// Disable PageUp/PageDown
parent.getActionMap().put("scrollDown", new AbstractAction(){
public void actionPerformed(ActionEvent e) {}});
parent.getActionMap().put("scrollUp", new AbstractAction(){
public void actionPerformed(ActionEvent e) {}});
parentViewport = parent.getViewport();
// Handle wheel scrolling
parent.setWheelScrollingEnabled(false);
addMouseWheelListener(this);
}
public Dimension getPreferredSize() {
int i = 0;
int maxX = 0;
int maxY = 0;
int c = 0;
while(imaxX) maxX = c-1;
maxY++;
c = 0;
}
c++;
i++;
}
if(c-1>maxX) maxX = c-1;
return new Dimension( maxX*charWidth + 2*mX , (maxY+1)*charHeight + 2*mY );
}
public void paint(Graphics g) {
int sX = mX;
int sY = mY;
Dimension d = getSize();
g.setColor(getBackground());
g.fillRect(0, 0, d.width, d.height);
g.setColor(defaultColor);
g.setFont(plainFont);
//Graphics2D g2 = (Graphics2D)g;
//g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
// RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
Color gf = null;
int sStart,sEnd;
if(selStart>selEnd) {
sStart = selEnd;
sEnd = selStart;
} else {
sStart = selStart;
sEnd = selEnd;
}
Point p = getPos(cursorPos);
int curLine = 0;
int curCol = 0;
String lineNb;
// Line number
g.setFont(italicFont);
g.setColor(countBackColor);
g.fillRect(0, sY - 1, mX - 2, charHeight + 3);
g.setColor(countForeColor);
lineNb = String.format("%03d",curLine);
g.drawString(lineNb,3,sY + charAscent);
for(int i=0;i= sStart && i < sEnd) {
g.setColor(selBackColor);
g.fillRect(sX, sY, charWidth, charHeight);
}
if( gf==null || gf.getRGB()!=text.getForeground(i) ) {
gf = new Color(text.getForeground(i));
}
g.setColor(gf);
switch (text.getStyle(i)) {
case Font.PLAIN:
g.setFont(plainFont);
break;
case Font.BOLD:
g.setFont(boldFont);
break;
case Font.ITALIC:
g.setFont(italicFont);
break;
}
}
if(c=='\n') {
sX = mX;
sY += charHeight;
curLine++;
curCol = 0;
// Line number
g.setFont(italicFont);
g.setColor(countBackColor);
g.fillRect(0,sY-1,mX-2,charHeight+3);
g.setColor(countForeColor);
lineNb = String.format("%03d",curLine);
g.drawString(lineNb,3,sY + charAscent);
} else if (c==' ') {
sX += charWidth;
curCol ++;
} else {
g.drawString(String.valueOf((char)c), sX, sY + charAscent);
sX += charWidth;
curCol ++;
}
}
if(cursorPos==text.length() && cursorVisible) {
g.setColor(Color.BLACK);
g.fillRect(sX-1,sY,2,charHeight);
}
}
// SeparatorList must be sorted
private final static char separatorList[] = {'"','(',')','*','+',',','-','/',':',';','=','{','}'};
private boolean isSeparator(int idx) {
/*
Arrays.sort(separatorList);
System.out.print("{");
for(int i=0;i=0);
}
@Override
public void mouseWheelMoved(MouseWheelEvent e) {
if( parentViewport != null ) {
final int scroll = e.getWheelRotation() * charHeight;
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
Point p = parentViewport.getViewPosition();
p.y += scroll;
if(p.y<0) p.y=0;
Dimension d = parentViewport.getSize();
Dimension dv = parentViewport.getViewSize();
if(p.y>dv.height-d.height) p.y = dv.height-d.height;
parentViewport.setViewPosition(p);
}
});
}
}
@Override
public void mouseClicked(MouseEvent e) {
if( e.getClickCount()==2 ) {
cursorPos = getCursorPos(e.getX(),e.getY());
lastCursorPos = cursorPos;
selStart = cursorPos;
selEnd = cursorPos;
while(selStart>=0 && !isSeparator(selStart))
selStart--;
selStart++;
while(selEnd<=text.length() && !isSeparator(selEnd))
selEnd++;
repaint();
}
}
@Override
public void mousePressed(MouseEvent e) {
requestFocus();
if(!isEditable)
return;
cursorPos = getCursorPos(e.getX(),e.getY());
lastCursorPos = cursorPos;
resetSelection();
isDragging = true;
repaint();
}
@Override
public void mouseReleased(MouseEvent e) {
if(!isEditable)
return;
isDragging = false;
repaint();
}
@Override
public void mouseEntered(MouseEvent e) {
}
@Override
public void mouseExited(MouseEvent e) {
}
@Override
public void mouseDragged(MouseEvent e) {
if(!isEditable)
return;
if( isDragging ) {
cursorPos = getCursorPos(e.getX(),e.getY());
lastCursorPos = cursorPos;
selEnd = cursorPos;
repaint();
}
}
@Override
public void mouseMoved(MouseEvent e) {
}
public void keyTyped(KeyEvent e) {
}
public void keyPressed(KeyEvent e) {
if(!isEditable)
return;
switch(e.getKeyCode()) {
case KeyEvent.VK_PAGE_UP:
int nbLine = getVisibleRect().height / charHeight;
while( getUpPos()>=0 && nbLine>0) {
cursorPos = getUpPos();
nbLine--;
}
resetSelection();
scrollToVisible();
repaint();
break;
case KeyEvent.VK_PAGE_DOWN:
nbLine = getVisibleRect().height / charHeight;
while( nbLine>0) {
cursorPos = getDownPos();
nbLine--;
}
resetSelection();
scrollToVisible();
repaint();
break;
case KeyEvent.VK_RIGHT:
cursorPos++;
if(cursorPos>=text.length()) cursorPos = text.length();
lastCursorPos = cursorPos;
if(e.isShiftDown()) {
selEnd = cursorPos;
} else {
resetSelection();
}
scrollToVisible();
repaint();
break;
case KeyEvent.VK_LEFT:
cursorPos--;
if(cursorPos<0) cursorPos = 0;
lastCursorPos = cursorPos;
if(e.isShiftDown()) {
selEnd = cursorPos;
} else {
resetSelection();
}
scrollToVisible();
repaint();
break;
case KeyEvent.VK_UP:
int s = getUpPos();
if(s>=0) {
cursorPos=s;
if(e.isShiftDown()) {
selEnd = cursorPos;
} else {
resetSelection();
}
scrollToVisible();
repaint();
}
break;
case KeyEvent.VK_DOWN:
cursorPos = getDownPos();
if(e.isShiftDown()) {
selEnd = cursorPos;
} else {
resetSelection();
}
scrollToVisible();
repaint();
break;
case KeyEvent.VK_BACK_SPACE:
if( hasSelection() ) {
modify();
deleteSelection();
fireUpdate();
} else {
if(cursorPos>0) {
modify();
cursorPos--;
lastCursorPos = cursorPos;
text.remove(cursorPos, 1);
resetSelection();
fireUpdate();
}
}
repaint();
scrollToVisible();
break;
case KeyEvent.VK_DELETE:
if( hasSelection() ) {
modify();
deleteSelection();
fireUpdate();
} else {
if(cursorPos=32 && c<=255) {
// Insert printable char
modify();
deleteSelection();
text.insert(c,cursorPos);
cursorPos++;
lastCursorPos++;
resetSelection();
fireUpdate();
scrollToVisible();
repaint();
} else {
// CTRL+Key
if( e.isControlDown() ) {
switch (e.getKeyCode()) {
case KeyEvent.VK_A:
selStart = 0;
selEnd = text.length();
repaint();
break;
case KeyEvent.VK_C:
copy();
break;
case KeyEvent.VK_V:
String str = getClipboardContent();
if( str!=null && str.length()>0 ) {
modify();
deleteSelection();
paste(str);
fireUpdate();
repaint();
scrollToVisible();
}
break;
case KeyEvent.VK_X:
if( hasSelection() ) {
modify();
copy();
deleteSelection();
fireUpdate();
repaint();
scrollToVisible();
}
break;
case KeyEvent.VK_Z:
if( !e.isShiftDown() ) {
// Undo
if( undoPos>0 ) {
if(undoPos==undoBuffer.size()) {
// We need to store present state
modify();
undoPos--;
}
undoPos--;
text.setText(undoBuffer.get(undoPos).text);
cursorPos = undoBuffer.get(undoPos).cursorPos;
fireUpdate();
repaint();
scrollToVisible();
}
} else {
// Redo
if( undoPos0;
}
private boolean deleteSelection() {
int length = Math.abs(selEnd - selStart);
if(length>0) {
int idx = Math.min(selStart,selEnd);
text.remove(idx,length);
cursorPos = idx;
resetSelection();
return true;
}
return false;
}
private void modify() {
// Reset undo buffer
int toRemove = undoBuffer.size() - undoPos;
for(int i=0;i MAX_UNDO ) {
toRemove = undoBuffer.size() - MAX_UNDO;
for(int i=0;i0) {
int idx = Math.min(selStart,selEnd);
String str = text.subString(idx,length);
StringSelection stringSelection = new StringSelection( str );
clipboard.setContents( stringSelection, null );
}
}
private String getClipboardContent() {
String str = null;
try {
str = (String)(clipboard.getData(DataFlavor.stringFlavor));
} catch (UnsupportedFlavorException e1) {
} catch (IOException e2) {
}
return str;
}
private void paste(String str) {
text.insert(str,cursorPos);
cursorPos += str.length();
resetSelection();
}
private int getCursorPos(int x,int y) {
int xP = (x-mX) - charWidth/2 + textCursorWidth;
int yP = (y-mY) / charHeight;
int yl = 0;
int i = 0;
while(ic) {
nc--;
cp--;
}
return cp;
} else {
return -1;
}
}
private int getDownPos() {
int cp = getNextLine(cursorPos);
int c = getColumn(cursorPos);
int nc = 0;
while(nc0 && !text.isNewLine(i-1)) {
i--;
c++;
}
return c;
}
private int getStartLine(int pos) {
int i = pos;
while(i>0 && !text.isNewLine(i-1))
i--;
return i;
}
private Point getPos(int pos) {
int i = 0;
int l = 0;
int c = 0;
while(i 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 = 0;
final int adjustDescent = 0;
final int adjustLeading = 0;
if (charAscent + adjustAscent >= 0)
charAscent += adjustAscent;
if (charDescent + adjustDescent >= 0)
charDescent += adjustDescent;
if (charLeading + adjustLeading >= 0)
charLeading += adjustLeading;
charHeight = charAscent + charDescent + charLeading;
mX = charWidth*4 + 4;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy