Mercurial Hosting > luan
changeset 1966:f2967524725b default tip
just override paintLayer
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Sun, 08 Jun 2025 09:28:26 -0600 |
parents | 7d091712bd82 |
children | |
files | src/luan/modules/swing/DefaultHighlighter.java src/luan/modules/swing/TextAreaLuan.java |
diffstat | 2 files changed, 67 insertions(+), 676 deletions(-) [+] |
line wrap: on
line diff
--- a/src/luan/modules/swing/DefaultHighlighter.java Sat Jun 07 23:53:55 2025 -0600 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,674 +0,0 @@ -/* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. - * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ -package luan.modules.swing; - -import java.util.Vector; -import java.awt.*; -import javax.swing.plaf.*; -import javax.swing.*; -//import javax.swing.text.*; -import javax.swing.text.LayeredHighlighter; -import javax.swing.text.JTextComponent; -import javax.swing.text.Highlighter; -import javax.swing.text.BadLocationException; -import javax.swing.text.View; -import javax.swing.text.Position; -import javax.swing.text.Document; -import goodjava.logging.Logger; -import goodjava.logging.LoggerFactory; - -/** - * Implements the Highlighter interfaces. Implements a simple highlight - * painter that renders in a solid color. - * - * @author Timothy Prinzing - with hack fix by fschmidt - * @see Highlighter - */ -public class DefaultHighlighter extends LayeredHighlighter { - private static final Logger logger = LoggerFactory.getLogger(DefaultHighlighter.class); - - /** - * Creates a new DefaultHighlighther object. - */ - public DefaultHighlighter() { - drawsLayeredHighlights = true; - } - - // ---- Highlighter methods ---------------------------------------------- - - /** - * Renders the highlights. - * - * @param g the graphics context - */ - public void paint(Graphics g) { - // PENDING(prinz) - should cull ranges not visible - int len = highlights.size(); - for (int i = 0; i < len; i++) { - HighlightInfo info = highlights.elementAt(i); - if (!(info instanceof LayeredHighlightInfo)) { - // Avoid allocing unless we need it. - Rectangle a = component.getBounds(); - Insets insets = component.getInsets(); - a.x = insets.left; - a.y = insets.top; - a.width -= insets.left + insets.right; - a.height -= insets.top + insets.bottom; - for (; i < len; i++) { - info = highlights.elementAt(i); - if (!(info instanceof LayeredHighlightInfo)) { - Highlighter.HighlightPainter p = info.getPainter(); - p.paint(g, info.getStartOffset(), info.getEndOffset(), - a, component); - } - } - } - } - } - - /** - * Called when the UI is being installed into the - * interface of a JTextComponent. Installs the editor, and - * removes any existing highlights. - * - * @param c the editor component - * @see Highlighter#install - */ - public void install(JTextComponent c) { - component = c; - removeAllHighlights(); - } - - /** - * Called when the UI is being removed from the interface of - * a JTextComponent. - * - * @param c the component - * @see Highlighter#deinstall - */ - public void deinstall(JTextComponent c) { - component = null; - } - - /** - * Adds a highlight to the view. Returns a tag that can be used - * to refer to the highlight. - * - * @param p0 the start offset of the range to highlight >= 0 - * @param p1 the end offset of the range to highlight >= p0 - * @param p the painter to use to actually render the highlight - * @return an object that can be used as a tag - * to refer to the highlight - * @exception BadLocationException if the specified location is invalid - */ - public Object addHighlight(int p0, int p1, Highlighter.HighlightPainter p) throws BadLocationException { - if (p0 < 0) { - throw new BadLocationException("Invalid start offset", p0); - } - - if (p1 < p0) { - throw new BadLocationException("Invalid end offset", p1); - } - - Document doc = component.getDocument(); - HighlightInfo i = (getDrawsLayeredHighlights() && - (p instanceof LayeredHighlighter.LayerPainter)) ? - new LayeredHighlightInfo() : new HighlightInfo(); - i.painter = p; - i.p0 = doc.createPosition(p0); - i.p1 = doc.createPosition(p1); - highlights.addElement(i); - safeDamageRange(p0, p1); - return i; - } - - /** - * Removes a highlight from the view. - * - * @param tag the reference to the highlight - */ - public void removeHighlight(Object tag) { - if (tag instanceof LayeredHighlightInfo) { - LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag; - if (lhi.width > 0 && lhi.height > 0) { - component.repaint(lhi.x, lhi.y, lhi.width, lhi.height); - } - } - else { - HighlightInfo info = (HighlightInfo) tag; - safeDamageRange(info.p0, info.p1); - } - highlights.removeElement(tag); - } - - /** - * Removes all highlights. - */ - public void removeAllHighlights() { - TextUI mapper = component.getUI(); - if (getDrawsLayeredHighlights()) { - int len = highlights.size(); - if (len != 0) { - int minX = 0; - int minY = 0; - int maxX = 0; - int maxY = 0; - int p0 = -1; - int p1 = -1; - for (int i = 0; i < len; i++) { - HighlightInfo hi = highlights.elementAt(i); - if (hi instanceof LayeredHighlightInfo) { - LayeredHighlightInfo info = (LayeredHighlightInfo)hi; - minX = Math.min(minX, info.x); - minY = Math.min(minY, info.y); - maxX = Math.max(maxX, info.x + info.width); - maxY = Math.max(maxY, info.y + info.height); - } - else { - if (p0 == -1) { - p0 = hi.p0.getOffset(); - p1 = hi.p1.getOffset(); - } - else { - p0 = Math.min(p0, hi.p0.getOffset()); - p1 = Math.max(p1, hi.p1.getOffset()); - } - } - } - if (minX != maxX && minY != maxY) { - component.repaint(minX, minY, maxX - minX, maxY - minY); - } - if (p0 != -1) { - try { - safeDamageRange(p0, p1); - } catch (BadLocationException e) {} - } - highlights.removeAllElements(); - } - } - else if (mapper != null) { - int len = highlights.size(); - if (len != 0) { - int p0 = Integer.MAX_VALUE; - int p1 = 0; - for (int i = 0; i < len; i++) { - HighlightInfo info = highlights.elementAt(i); - p0 = Math.min(p0, info.p0.getOffset()); - p1 = Math.max(p1, info.p1.getOffset()); - } - try { - safeDamageRange(p0, p1); - } catch (BadLocationException e) {} - - highlights.removeAllElements(); - } - } - } - - /** - * Changes a highlight. - * - * @param tag the highlight tag - * @param p0 the beginning of the range >= 0 - * @param p1 the end of the range >= p0 - * @exception BadLocationException if the specified location is invalid - */ - public void changeHighlight(Object tag, int p0, int p1) throws BadLocationException { - if (p0 < 0) { - throw new BadLocationException("Invalid beginning of the range", p0); - } - - if (p1 < p0) { - throw new BadLocationException("Invalid end of the range", p1); - } - - Document doc = component.getDocument(); - if (tag instanceof LayeredHighlightInfo) { - LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag; - if (lhi.width > 0 && lhi.height > 0) { - component.repaint(lhi.x, lhi.y, lhi.width, lhi.height); - } - // Mark the highlights region as invalid, it will reset itself - // next time asked to paint. - lhi.width = lhi.height = 0; - lhi.p0 = doc.createPosition(p0); - lhi.p1 = doc.createPosition(p1); - safeDamageRange(Math.min(p0, p1), Math.max(p0, p1)); - } - else { - HighlightInfo info = (HighlightInfo) tag; - int oldP0 = info.p0.getOffset(); - int oldP1 = info.p1.getOffset(); - if (p0 == oldP0) { - safeDamageRange(Math.min(oldP1, p1), - Math.max(oldP1, p1)); - } else if (p1 == oldP1) { - safeDamageRange(Math.min(p0, oldP0), - Math.max(p0, oldP0)); - } else { - safeDamageRange(oldP0, oldP1); - safeDamageRange(p0, p1); - } - info.p0 = doc.createPosition(p0); - info.p1 = doc.createPosition(p1); - } - } - - /** - * Makes a copy of the highlights. Does not actually clone each highlight, - * but only makes references to them. - * - * @return the copy - * @see Highlighter#getHighlights - */ - public Highlighter.Highlight[] getHighlights() { - int size = highlights.size(); - if (size == 0) { - return noHighlights; - } - Highlighter.Highlight[] h = new Highlighter.Highlight[size]; - highlights.copyInto(h); - return h; - } - - /** - * When leaf Views (such as LabelView) are rendering they should - * call into this method. If a highlight is in the given region it will - * be drawn immediately. - * - * @param g Graphics used to draw - * @param p0 starting offset of view - * @param p1 ending offset of view - * @param viewBounds Bounds of View - * @param editor JTextComponent - * @param view View instance being rendered - */ - public void paintLayeredHighlights(Graphics g, int p0, int p1, - Shape viewBounds, - JTextComponent editor, View view) { - for (int counter = highlights.size() - 1; counter >= 0; counter--) { - HighlightInfo tag = highlights.elementAt(counter); - if (tag instanceof LayeredHighlightInfo) { - LayeredHighlightInfo lhi = (LayeredHighlightInfo)tag; - int start = lhi.getStartOffset(); - int end = lhi.getEndOffset(); - if ((p0 < start && p1 > start) || - (p0 >= start && p0 < end)) { - lhi.paintLayeredHighlights(g, p0, p1, viewBounds, - editor, view); - } - } - } - } - - /** - * Queues damageRange() call into event dispatch thread - * to be sure that views are in consistent state. - */ - private void safeDamageRange(final Position p0, final Position p1) { - safeDamager.damageRange(p0, p1); - } - - /** - * Queues damageRange() call into event dispatch thread - * to be sure that views are in consistent state. - */ - private void safeDamageRange(int a0, int a1) throws BadLocationException { - Document doc = component.getDocument(); - safeDamageRange(doc.createPosition(a0), doc.createPosition(a1)); - } - - /** - * If true, highlights are drawn as the Views draw the text. That is - * the Views will call into <code>paintLayeredHighlight</code> which - * will result in a rectangle being drawn before the text is drawn - * (if the offsets are in a highlighted region that is). For this to - * work the painter supplied must be an instance of - * LayeredHighlightPainter. - */ - public void setDrawsLayeredHighlights(boolean newValue) { - drawsLayeredHighlights = newValue; - } - - public boolean getDrawsLayeredHighlights() { - return drawsLayeredHighlights; - } - - // ---- member variables -------------------------------------------- - - private final static Highlighter.Highlight[] noHighlights = - new Highlighter.Highlight[0]; - private Vector<HighlightInfo> highlights = new Vector<HighlightInfo>(); - private JTextComponent component; - private boolean drawsLayeredHighlights; - private SafeDamager safeDamager = new SafeDamager(); - - - /** - * Default implementation of LayeredHighlighter.LayerPainter that can - * be used for painting highlights. - * <p> - * As of 1.4 this field is final. - */ - public static final LayeredHighlighter.LayerPainter DefaultPainter = new DefaultHighlightPainter(null); - - - /** - * Simple highlight painter that fills a highlighted area with - * a solid color. - */ - public static class DefaultHighlightPainter extends LayeredHighlighter.LayerPainter { - - /** - * Constructs a new highlight painter. If <code>c</code> is null, - * the JTextComponent will be queried for its selection color. - * - * @param c the color for the highlight - */ - public DefaultHighlightPainter(Color c) { - color = c; - } - - /** - * Returns the color of the highlight. - * - * @return the color - */ - public Color getColor() { - return color; - } - - // --- HighlightPainter methods --------------------------------------- - - /** - * Paints a highlight. - * - * @param g the graphics context - * @param offs0 the starting model offset >= 0 - * @param offs1 the ending model offset >= offs1 - * @param bounds the bounding box for the highlight - * @param c the editor - */ - public void paint(Graphics g, int offs0, int offs1, Shape bounds, JTextComponent c) { - Rectangle alloc = bounds.getBounds(); - try { - // --- determine locations --- - TextUI mapper = c.getUI(); - Rectangle p0 = mapper.modelToView(c, offs0); - Rectangle p1 = mapper.modelToView(c, offs1); - - // --- render --- - Color color = getColor(); - - if (color == null) { - g.setColor(c.getSelectionColor()); - } - else { - g.setColor(color); - } - if (p0.y == p1.y) { - // same line, render a rectangle - Rectangle r = p0.union(p1); - g.fillRect(r.x, r.y, r.width, r.height); - } else { - // different lines - int p0ToMarginWidth = alloc.x + alloc.width - p0.x; - g.fillRect(p0.x, p0.y, p0ToMarginWidth, p0.height); - if ((p0.y + p0.height) != p1.y) { - g.fillRect(alloc.x, p0.y + p0.height, alloc.width, - p1.y - (p0.y + p0.height)); - } - g.fillRect(alloc.x, p1.y, (p1.x - alloc.x), p1.height); - } - } catch (BadLocationException e) { - // can't render - } - } - - // --- LayerPainter methods ---------------------------- - /** - * Paints a portion of a highlight. - * - * @param g the graphics context - * @param offs0 the starting model offset >= 0 - * @param offs1 the ending model offset >= offs1 - * @param bounds the bounding box of the view, which is not - * necessarily the region to paint. - * @param c the editor - * @param view View painting for - * @return region drawing occurred in - */ - public Shape paintLayer(Graphics g, int offs0, int offs1, - Shape bounds, JTextComponent c, View view) { - Color color = getColor(); - - if (color == null) { - g.setColor(c.getSelectionColor()); - } - else { - g.setColor(color); - } - - Rectangle r; - - if (offs0 == view.getStartOffset() && - offs1 == view.getEndOffset()) { - // Contained in view, can just use bounds. - if (bounds instanceof Rectangle) { - r = (Rectangle) bounds; - } - else { - r = bounds.getBounds(); - } - } - else { - // Should only render part of View. - try { - // --- determine locations --- - Shape shape = view.modelToView(offs0, Position.Bias.Forward, - offs1,Position.Bias.Backward, - bounds); - r = (shape instanceof Rectangle) ? - (Rectangle)shape : shape.getBounds(); - // view.modelToView is broken crap, so hack a fix - fschmidt - int height = c.getFontMetrics(c.getFont()).getHeight(); - if( offs0 == offs1 && r.height >= 2*height ) { - r.y = r.y + r.height - height; - r.height = height; - r.width = 1; - } - // logger.info("offs0="+offs0+" offs1="+offs1+" shape="+shape+" bounds="+bounds); - // end hack - } catch (BadLocationException e) { - // can't render - r = null; - } - } - - if (r != null) { - // If we are asked to highlight, we should draw something even - // if the model-to-view projection is of zero width (6340106). - r.width = Math.max(r.width, 1); - - g.fillRect(r.x, r.y, r.width, r.height); - } - - return r; - } - - private Color color; - - } - - - class HighlightInfo implements Highlighter.Highlight { - - public int getStartOffset() { - return p0.getOffset(); - } - - public int getEndOffset() { - return p1.getOffset(); - } - - public Highlighter.HighlightPainter getPainter() { - return painter; - } - - Position p0; - Position p1; - Highlighter.HighlightPainter painter; - } - - - /** - * LayeredHighlightPainter is used when a drawsLayeredHighlights is - * true. It maintains a rectangle of the region to paint. - */ - class LayeredHighlightInfo extends HighlightInfo { - - void union(Shape bounds) { - if (bounds == null) - return; - - Rectangle alloc; - if (bounds instanceof Rectangle) { - alloc = (Rectangle)bounds; - } - else { - alloc = bounds.getBounds(); - } - if (width == 0 || height == 0) { - x = alloc.x; - y = alloc.y; - width = alloc.width; - height = alloc.height; - } - else { - width = Math.max(x + width, alloc.x + alloc.width); - height = Math.max(y + height, alloc.y + alloc.height); - x = Math.min(x, alloc.x); - width -= x; - y = Math.min(y, alloc.y); - height -= y; - } - } - - /** - * Restricts the region based on the receivers offsets and messages - * the painter to paint the region. - */ - void paintLayeredHighlights(Graphics g, int p0, int p1, - Shape viewBounds, JTextComponent editor, - View view) { - int start = getStartOffset(); - int end = getEndOffset(); - // Restrict the region to what we represent - p0 = Math.max(start, p0); - p1 = Math.min(end, p1); - // Paint the appropriate region using the painter and union - // the effected region with our bounds. - union(((LayeredHighlighter.LayerPainter)painter).paintLayer - (g, p0, p1, viewBounds, editor, view)); - } - - int x; - int y; - int width; - int height; - } - - /** - * This class invokes <code>mapper.damageRange</code> in - * EventDispatchThread. The only one instance per Highlighter - * is cretaed. When a number of ranges should be damaged - * it collects them into queue and damages - * them in consecutive order in <code>run</code> - * call. - */ - class SafeDamager implements Runnable { - private Vector<Position> p0 = new Vector<Position>(10); - private Vector<Position> p1 = new Vector<Position>(10); - private Document lastDoc = null; - - /** - * Executes range(s) damage and cleans range queue. - */ - public synchronized void run() { - if (component != null) { - TextUI mapper = component.getUI(); - if (mapper != null && lastDoc == component.getDocument()) { - // the Document should be the same to properly - // display highlights - int len = p0.size(); - for (int i = 0; i < len; i++){ - mapper.damageRange(component, - p0.get(i).getOffset(), - p1.get(i).getOffset()); - } - } - } - p0.clear(); - p1.clear(); - - // release reference - lastDoc = null; - } - - /** - * Adds the range to be damaged into the range queue. If the - * range queue is empty (the first call or run() was already - * invoked) then adds this class instance into EventDispatch - * queue. - * - * The method also tracks if the current document changed or - * component is null. In this case it removes all ranges added - * before from range queue. - */ - public synchronized void damageRange(Position pos0, Position pos1) { - if (component == null) { - p0.clear(); - lastDoc = null; - return; - } - - boolean addToQueue = p0.isEmpty(); - Document curDoc = component.getDocument(); - if (curDoc != lastDoc) { - if (!p0.isEmpty()) { - p0.clear(); - p1.clear(); - } - lastDoc = curDoc; - } - p0.add(pos0); - p1.add(pos1); - - if (addToQueue) { - SwingUtilities.invokeLater(this); - } - } - } -}
--- a/src/luan/modules/swing/TextAreaLuan.java Sat Jun 07 23:53:55 2025 -0600 +++ b/src/luan/modules/swing/TextAreaLuan.java Sun Jun 08 09:28:26 2025 -0600 @@ -5,6 +5,7 @@ import java.awt.Rectangle; import java.awt.Graphics; import java.awt.Color; +import java.awt.Shape; import java.awt.event.FocusListener; import java.awt.event.FocusEvent; import java.awt.event.ActionListener; @@ -20,6 +21,9 @@ import javax.swing.text.Segment; import javax.swing.text.DefaultEditorKit; import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; +import javax.swing.text.Position; +import javax.swing.text.DefaultHighlighter; import javax.swing.event.DocumentEvent; import goodjava.logging.Logger; import goodjava.logging.LoggerFactory; @@ -46,8 +50,70 @@ setVisible(true); } + @Override protected Highlighter.HighlightPainter getSelectionPainter() { - return DefaultHighlighter.DefaultPainter; + return defaultPainter; + } + }; + + public static final DefaultHighlighter.DefaultHighlightPainter defaultPainter = new DefaultHighlighter.DefaultHighlightPainter(null) { + // copied from DefaultHighlighter + public Shape paintLayer(Graphics g, int offs0, int offs1, + Shape bounds, JTextComponent c, View view) { + Color color = getColor(); + + if (color == null) { + g.setColor(c.getSelectionColor()); + } + else { + g.setColor(color); + } + + Rectangle r; + + if (offs0 == view.getStartOffset() && + offs1 == view.getEndOffset()) { + // Contained in view, can just use bounds. + if (bounds instanceof Rectangle) { + r = (Rectangle) bounds; + } + else { + r = bounds.getBounds(); + } + } + else { + // Should only render part of View. + try { + // --- determine locations --- + Shape shape = view.modelToView(offs0, Position.Bias.Forward, + offs1,Position.Bias.Backward, + bounds); + r = (shape instanceof Rectangle) ? + (Rectangle)shape : shape.getBounds(); + // view.modelToView is broken crap, so hack a fix - fschmidt + int height = c.getFontMetrics(c.getFont()).getHeight(); + if( offs0 == offs1 && r.height >= 2*height ) { + r.y = r.y + r.height - height; + r.height = height; + r.width = 1; + } + // logger.info("offs0="+offs0+" offs1="+offs1+" shape="+shape+" bounds="+bounds); + // end hack + } catch (BadLocationException e) { + // can't render + r = null; + } + } + + if (r != null) { + // If we are asked to highlight, we should draw something even + // if the model-to-view projection is of zero width (6340106). + r.width = Math.max(r.width, 1); + + g.fillRect(r.x, r.y, r.width, r.height); + } + + return r; } }; @@ -116,7 +182,6 @@ public TextAreaLuan() { super(); //logger.info(""+getFont().getSize()); - setHighlighter(new DefaultHighlighter()); if( UIManager.getLookAndFeel().getName().startsWith("FlatLaf") ) { setCaret(flatLafCaret); }