Mercurial Hosting > luan
changeset 1948:d90f12acb222 default tip
better TextAreaLineNumbersLuan
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 22 May 2025 14:29:11 -0600 |
parents | 6538936ac108 |
children | |
files | src/luan/modules/swing/TextAreaLineNumbersLuan.java src/luan/modules/swing/TextAreaLineNumbersLuanOld.java src/luan/modules/swing/TextAreaLuan.java |
diffstat | 3 files changed, 350 insertions(+), 157 deletions(-) [+] |
line wrap: on
line diff
--- a/src/luan/modules/swing/TextAreaLineNumbersLuan.java Wed May 21 11:49:22 2025 -0600 +++ b/src/luan/modules/swing/TextAreaLineNumbersLuan.java Thu May 22 14:29:11 2025 -0600 @@ -1,59 +1,154 @@ package luan.modules.swing; -import java.beans.PropertyChangeListener; -import java.beans.PropertyChangeEvent; -import java.awt.Font; -import java.awt.Component; +import java.util.List; +import java.util.ArrayList; import java.awt.Dimension; -import java.awt.Color; -import java.awt.event.ComponentListener; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; +import java.awt.Rectangle; +import java.awt.Insets; +import java.awt.Graphics; +import java.awt.FontMetrics; +import java.awt.Point; import java.awt.event.MouseListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -import javax.swing.JPanel; -import javax.swing.BoxLayout; -import javax.swing.JLabel; +import java.awt.event.MouseMotionListener; +import java.awt.event.MouseMotionAdapter; +import javax.swing.JComponent; import javax.swing.SwingUtilities; import javax.swing.event.DocumentListener; import javax.swing.event.DocumentEvent; import javax.swing.text.BadLocationException; +import javax.swing.border.Border; +import javax.swing.border.EmptyBorder; import goodjava.logging.Logger; import goodjava.logging.LoggerFactory; -public class TextAreaLineNumbersLuan extends JPanel implements DocumentUpdateListener { +public class TextAreaLineNumbersLuan extends JComponent implements DocumentUpdateListener { private static final Logger logger = LoggerFactory.getLogger(TextAreaLineNumbersLuan.class); + private static final List<String> intStrings = new ArrayList<String>(); + + private static String intToString(int i) { + synchronized(intStrings) { + while( intStrings.size() <= i ) { + intStrings.add(Integer.toString(intStrings.size())); + } + return intStrings.get(i); + } + } + private final TextAreaLuan textArea; - private int width; - private int lines; + private int currentLine = -1; + private int height; private int lineStartSelection = -1; private final LuanDocumentListener ldl = new LuanDocumentListener(this); public TextAreaLineNumbersLuan(TextAreaLuan textArea) { + Insets textInsets = textArea.getInsets(); + Border border = new EmptyBorder(textInsets.top,0,textInsets.bottom,0); + setBorder(border); this.textArea = textArea; - this.lines = textArea.getLineCount(); //logger.info("lines "+lines); setFont(textArea.getFont()); - setLayout( new BoxLayout(this, BoxLayout.Y_AXIS) ); - for( int i=0; i<lines; i++ ) { - JLabel label = newJLabel(i); - add(label); + textArea.getDocument().addDocumentListener(new WeakDocumentListener(ldl)); + addMouseListener(mouseListener); + addMouseMotionListener(mouseDragListener); + } + + @Override public void setBorder(Border border) { + Insets borderInsets = ((EmptyBorder)border).getBorderInsets(); + Insets insets = getInsets(); + border = new EmptyBorder(insets.top,borderInsets.left,insets.bottom,borderInsets.right); + super.setBorder(border); + } + + private int getInnerHeight() { + try { + Rectangle rect = textArea.modelToView(textArea.getText().length()); + int height = rect!=null ? rect.y + rect.height : textArea.getRowHeight(); + //logger.info("height = "+height); + //if(rect!=null) logger.info("height2 = "+(rect.y+rect.height)); + this.height = height; + return height; + } catch(BadLocationException e) { + throw new RuntimeException(e); } - fixWidth(); - fixHeights(); - textArea.getDocument().addDocumentListener(new WeakDocumentListener(ldl)); - textArea.addPropertyChangeListener("lineWrap",lineWrapListener); - textArea.addComponentListener(resizeListener); + } + + @Override public Dimension getPreferredSize() { + //logger.info("getPreferredSize"); + int height = getInnerHeight(); + int lines = textArea.getLineCount(); + int width = getFontMetrics(getFont()).stringWidth(intToString(lines)); + //logger.info("lines = "+lines); + //logger.info("height = "+height); + Insets insets = getInsets(); + height += insets.top + insets.bottom; + width += insets.left + insets.right; + return new Dimension(width, height); + } + + @Override protected void paintComponent(Graphics g) { + //logger.info("paintComponent"); + super.paintComponent(g); + FontMetrics fm = g.getFontMetrics(); + int lines = textArea.getLineCount(); + int xRight = getInsets().left + fm.stringWidth(intToString(lines)); + try { + for( int i = 0; i < lines; i++ ) { + int start = textArea.getLineStartOffset(i); + Rectangle rect = textArea.modelToView(start); + String text = intToString(i+1); + int x = xRight - fm.stringWidth(text); + int y = rect.y + fm.getAscent(); + g.drawString( text, x, y ); + } + } catch(BadLocationException e) { + throw new RuntimeException(e); + } + } + + @Override public void updated(final DocumentEvent event) { + SwingUtilities.invokeLater(new Runnable(){public void run(){ + //logger.info(event.getType().toString()+" "+event.getOffset()+" "+event.getLength()); + try { + int startOffset = event.getOffset(); + int startLine = textArea.getLineOfOffset(startOffset); + int endOffset = startOffset + event.getLength(); + endOffset = Math.min( endOffset, textArea.getText().length() ); + int endLine = textArea.getLineOfOffset(endOffset); + //logger.info("lines "+startLine+" to "+endLine); + if( startLine == endLine ) { + if( currentLine == startLine && height == getInnerHeight() ) + return; + currentLine = startLine; + } else { + currentLine = -1; + } + } catch(BadLocationException e) { + throw new RuntimeException(e); + } + revalidate(); + repaint(); + }}); + } + + private int getLine(MouseEvent event) { + try { + Point point = event.getPoint(); + point.x = 0; + int pos = textArea.viewToModel(point); + return textArea.getLineOfOffset(pos); + } catch(BadLocationException e) { + throw new RuntimeException(e); + } } private final MouseListener mouseListener = new MouseAdapter() { @Override public void mousePressed(MouseEvent event) { if( event.getButton() == MouseEvent.BUTTON1 ) { - JLabel label = (JLabel)event.getComponent(); - int line = (Integer)label.getClientProperty("line"); + int line = getLine(event); //logger.info("clicked "+line); lineStartSelection = line; int start, end; @@ -72,11 +167,11 @@ if( event.getButton() == MouseEvent.BUTTON1 ) lineStartSelection = -1; } - - @Override public void mouseEntered(MouseEvent event) { + }; + private final MouseMotionListener mouseDragListener = new MouseMotionAdapter() { + @Override public void mouseDragged(MouseEvent event) { if( lineStartSelection >= 0 && (event.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 ) { - JLabel label = (JLabel)event.getComponent(); - int line = (Integer)label.getClientProperty("line"); + int line = getLine(event); //logger.info("entered "+line); int start, end; try { @@ -91,131 +186,4 @@ } }; - private JLabel newJLabel(int line) { - String text = String.valueOf(line+1); - JLabel label = new JLabel(text,JLabel.RIGHT); - label.setVerticalAlignment(JLabel.TOP); - label.setFont(getFont()); - label.setForeground(getForeground()); - label.putClientProperty("line",line); - label.addMouseListener(mouseListener); - return label; - } - - private void fixWidth() { - Component last = getComponent(getComponentCount()-1); - int lastWidth = last.getPreferredSize().width; - //logger.info("lastWidth "+lastWidth); - if( width != lastWidth ) { - width = lastWidth; - for( Component label : getComponents() ) { - Dimension size = label.getPreferredSize(); - Dimension newSize = new Dimension( width, size.height ); - //label.setPreferredSize(newSize); - label.setMaximumSize(newSize); - //label.revalidate(); - //logger.info("setPreferredSize "+label.getPreferredSize().width); - } - //revalidate(); - //repaint(); - //logger.info("firstWidth0 "+getComponent(0).getSize().width); - } - } - - private void doFixHeights(int start,int end) { - boolean changed = false; - for( int i=start; i<=end; i++ ) { - Component label = getComponent(i); - Dimension size = label.getMaximumSize(); - int height; - try { - height = textArea.getLineHeight(i); - } catch(BadLocationException e) { - throw new RuntimeException(e); - } - //logger.info("height "+i+" "+height); - if( height != size.height ) { - changed = true; - //logger.info("width "+i+" "+size.width); - Dimension newSize = new Dimension( size.width, height ); - //label.setPreferredSize(newSize); - label.setMaximumSize(newSize); - } - } - if( changed ) - revalidate(); - } - - private void fixHeights(final int start,final int end) { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - doFixHeights(start,end); - } - }); - } - - private void fixHeights() { - fixHeights(0,lines-1); - } - - @Override public void setForeground(Color fg) { - super.setForeground(fg); - for( Component label : getComponents() ) { - label.setForeground(fg); - } - } - - @Override public void updated(DocumentEvent event) { - //logger.info(event.getType().toString()+" "+event.getOffset()+" "+event.getLength()); - int n = textArea.getLineCount(); - if( lines == n ) { - if( textArea.getLineWrap() ) { - int offset = event.getOffset(); - int start, end; - try { - start = textArea.getLineOfOffset(offset); - int i = offset + event.getLength() - 1; - i = Math.min( i, textArea.getText().length() ); - end = textArea.getLineOfOffset(i); - } catch(BadLocationException e) { - throw new RuntimeException(e); - } - fixHeights(start,end); - } - } else if( lines < n ) { - for( int i=lines; i<n; i++ ) { - JLabel label = newJLabel(i); - add(label); - } - lines = n; - fixWidth(); - if( textArea.getLineWrap() ) - fixHeights(); - } else { // lines > n - for( int i=n; i<lines; i++ ) { - //logger.info("getComponentCount "+getComponentCount()); - //logger.info("remove "+n); - remove(n); - //logger.info("getComponentCount "+getComponentCount()); - } - lines = n; - fixWidth(); - if( textArea.getLineWrap() ) - fixHeights(); - repaint(); - } - } - - private final PropertyChangeListener lineWrapListener = new PropertyChangeListener() { - @Override public void propertyChange(PropertyChangeEvent evt) { - fixHeights(); - } - }; - - private final ComponentListener resizeListener = new ComponentAdapter() { - @Override public void componentResized(ComponentEvent e) { - if( textArea.getLineWrap() ) - fixHeights(); - } - }; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/modules/swing/TextAreaLineNumbersLuanOld.java Thu May 22 14:29:11 2025 -0600 @@ -0,0 +1,221 @@ +package luan.modules.swing; + +import java.beans.PropertyChangeListener; +import java.beans.PropertyChangeEvent; +import java.awt.Font; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Color; +import java.awt.event.ComponentListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.MouseListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import javax.swing.JPanel; +import javax.swing.BoxLayout; +import javax.swing.JLabel; +import javax.swing.SwingUtilities; +import javax.swing.event.DocumentListener; +import javax.swing.event.DocumentEvent; +import javax.swing.text.BadLocationException; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; + + +public class TextAreaLineNumbersLuanOld extends JPanel implements DocumentUpdateListener { + private static final Logger logger = LoggerFactory.getLogger(TextAreaLineNumbersLuanOld.class); + + private final TextAreaLuan textArea; + private int width; + private int lines; + private int lineStartSelection = -1; + private final LuanDocumentListener ldl = new LuanDocumentListener(this); + + public TextAreaLineNumbersLuanOld(TextAreaLuan textArea) { + this.textArea = textArea; + this.lines = textArea.getLineCount(); + //logger.info("lines "+lines); + setFont(textArea.getFont()); + setLayout( new BoxLayout(this, BoxLayout.Y_AXIS) ); + for( int i=0; i<lines; i++ ) { + JLabel label = newJLabel(i); + add(label); + } + fixWidth(); + fixHeights(); + textArea.getDocument().addDocumentListener(new WeakDocumentListener(ldl)); + textArea.addPropertyChangeListener("lineWrap",lineWrapListener); + textArea.addComponentListener(resizeListener); + } + + private final MouseListener mouseListener = new MouseAdapter() { + @Override public void mousePressed(MouseEvent event) { + if( event.getButton() == MouseEvent.BUTTON1 ) { + JLabel label = (JLabel)event.getComponent(); + int line = (Integer)label.getClientProperty("line"); + //logger.info("clicked "+line); + lineStartSelection = line; + int start, end; + try { + start = textArea.getLineStartOffset(line); + end = textArea.getLineEndOffset(line); + } catch(BadLocationException e) { + throw new RuntimeException(e); + } + textArea.setCaretPosition(start); + textArea.moveCaretPosition(end); + } + } + + @Override public void mouseReleased(MouseEvent event) { + if( event.getButton() == MouseEvent.BUTTON1 ) + lineStartSelection = -1; + } + + @Override public void mouseEntered(MouseEvent event) { + if( lineStartSelection >= 0 && (event.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) != 0 ) { + JLabel label = (JLabel)event.getComponent(); + int line = (Integer)label.getClientProperty("line"); + //logger.info("entered "+line); + int start, end; + try { + start = textArea.getLineStartOffset(Math.min(line,lineStartSelection)); + end = textArea.getLineEndOffset(Math.max(line,lineStartSelection)); + } catch(BadLocationException e) { + throw new RuntimeException(e); + } + textArea.setCaretPosition(start); + textArea.moveCaretPosition(end); + } + } + }; + + private JLabel newJLabel(int line) { + String text = String.valueOf(line+1); + JLabel label = new JLabel(text,JLabel.RIGHT); + label.setVerticalAlignment(JLabel.TOP); + label.setFont(getFont()); + label.setForeground(getForeground()); + label.putClientProperty("line",line); + label.addMouseListener(mouseListener); + return label; + } + + private void fixWidth() { + Component last = getComponent(getComponentCount()-1); + int lastWidth = last.getPreferredSize().width; + //logger.info("lastWidth "+lastWidth); + if( width != lastWidth ) { + width = lastWidth; + for( Component label : getComponents() ) { + Dimension size = label.getPreferredSize(); + Dimension newSize = new Dimension( width, size.height ); + //label.setPreferredSize(newSize); + label.setMaximumSize(newSize); + //label.revalidate(); + //logger.info("setPreferredSize "+label.getPreferredSize().width); + } + //revalidate(); + //repaint(); + //logger.info("firstWidth0 "+getComponent(0).getSize().width); + } + } + + private void doFixHeights(int start,int end) { + boolean changed = false; + for( int i=start; i<=end; i++ ) { + Component label = getComponent(i); + Dimension size = label.getMaximumSize(); + int height; + try { + height = textArea.getLineHeight(i); + } catch(BadLocationException e) { + throw new RuntimeException(e); + } + //logger.info("height "+i+" "+height); + if( height != size.height ) { + changed = true; + //logger.info("width "+i+" "+size.width); + Dimension newSize = new Dimension( size.width, height ); + //label.setPreferredSize(newSize); + label.setMaximumSize(newSize); + } + } + if( changed ) + revalidate(); + } + + private void fixHeights(final int start,final int end) { + SwingUtilities.invokeLater(new Runnable() { + public void run() { + doFixHeights(start,end); + } + }); + } + + private void fixHeights() { + fixHeights(0,lines-1); + } + + @Override public void setForeground(Color fg) { + super.setForeground(fg); + for( Component label : getComponents() ) { + label.setForeground(fg); + } + } + + @Override public void updated(DocumentEvent event) { + //logger.info(event.getType().toString()+" "+event.getOffset()+" "+event.getLength()); + int n = textArea.getLineCount(); + if( lines == n ) { + if( textArea.getLineWrap() ) { + int offset = event.getOffset(); + int start, end; + try { + start = textArea.getLineOfOffset(offset); + int i = offset + event.getLength() - 1; + i = Math.min( i, textArea.getText().length() ); + end = textArea.getLineOfOffset(i); + } catch(BadLocationException e) { + throw new RuntimeException(e); + } + fixHeights(start,end); + } + } else if( lines < n ) { + for( int i=lines; i<n; i++ ) { + JLabel label = newJLabel(i); + add(label); + } + lines = n; + fixWidth(); + if( textArea.getLineWrap() ) + fixHeights(); + } else { // lines > n + for( int i=n; i<lines; i++ ) { + //logger.info("getComponentCount "+getComponentCount()); + //logger.info("remove "+n); + remove(n); + //logger.info("getComponentCount "+getComponentCount()); + } + lines = n; + fixWidth(); + if( textArea.getLineWrap() ) + fixHeights(); + repaint(); + } + } + + private final PropertyChangeListener lineWrapListener = new PropertyChangeListener() { + @Override public void propertyChange(PropertyChangeEvent evt) { + fixHeights(); + } + }; + + private final ComponentListener resizeListener = new ComponentAdapter() { + @Override public void componentResized(ComponentEvent e) { + if( textArea.getLineWrap() ) + fixHeights(); + } + }; +}
--- a/src/luan/modules/swing/TextAreaLuan.java Wed May 21 11:49:22 2025 -0600 +++ b/src/luan/modules/swing/TextAreaLuan.java Thu May 22 14:29:11 2025 -0600 @@ -65,6 +65,10 @@ getDocument().addDocumentListener(new WeakDocumentListener(ldl)); } + @Override public int getRowHeight() { + return super.getRowHeight(); + } + public int getLineHeight(int line) throws BadLocationException { if( !getLineWrap() ) return getRowHeight();