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();