All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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