From e8c27e9a2f294c5061e8ef4b8e2192f3f59e4536 Mon Sep 17 00:00:00 2001 From: Alexander Scherbatiy Date: Thu, 20 Oct 2016 12:18:15 +0300 Subject: [PATCH] 8156217: Selected text is shifted on HiDPI display Reviewed-by: prr, serb --- .../classes/javax/swing/plaf/TextUI.java | 20 + .../javax/swing/plaf/basic/BasicTextUI.java | 56 +- .../javax/swing/plaf/multi/MultiTextUI.java | 35 ++ .../javax/swing/text/GlyphPainter1.java | 15 +- .../javax/swing/text/GlyphPainter2.java | 5 +- .../javax/swing/text/JTextComponent.java | 52 ++ .../javax/swing/text/ParagraphView.java | 8 +- .../javax/swing/text/PasswordView.java | 125 ++++- .../classes/javax/swing/text/PlainView.java | 226 ++++++-- .../classes/javax/swing/text/Utilities.java | 271 +++++++--- .../javax/swing/text/WrappedPlainView.java | 191 +++++-- .../classes/sun/swing/SwingUtilities2.java | 86 ++- .../accessibility/internal/AccessBridge.java | 4 +- .../8163124/CaretFloatingPointAPITest.java | 447 ++++++++++++++++ .../8156217/TextSelectionTest.java | 108 ++++ .../text/View/8156217/FPMethodCalledTest.java | 499 ++++++++++++++++++ 16 files changed, 1936 insertions(+), 212 deletions(-) create mode 100644 jdk/test/javax/swing/text/Caret/8163124/CaretFloatingPointAPITest.java create mode 100644 jdk/test/javax/swing/text/JTextComponent/8156217/TextSelectionTest.java create mode 100644 jdk/test/javax/swing/text/View/8156217/FPMethodCalledTest.java diff --git a/jdk/src/java.desktop/share/classes/javax/swing/plaf/TextUI.java b/jdk/src/java.desktop/share/classes/javax/swing/plaf/TextUI.java index 3ce2e8925f9..66d215b92b2 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/TextUI.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/TextUI.java @@ -46,7 +46,11 @@ public abstract class TextUI extends ComponentUI * @return the coordinates as a {@code Rectangle} * @exception BadLocationException if the given position does not * represent a valid location in the associated document + * + * @deprecated replaced by + * {@link #modelToView2D(JTextComponent, int, Position.Bias)} */ + @Deprecated(since = "9") public abstract Rectangle modelToView(JTextComponent t, int pos) throws BadLocationException; /** @@ -59,7 +63,11 @@ public abstract class TextUI extends ComponentUI * @return the coordinates as a {@code Rectangle} * @exception BadLocationException if the given position does not * represent a valid location in the associated document + * + * @deprecated replaced by + * {@link #modelToView2D(JTextComponent, int, Position.Bias)} */ + @Deprecated(since = "9") public abstract Rectangle modelToView(JTextComponent t, int pos, Position.Bias bias) throws BadLocationException; /** @@ -92,7 +100,11 @@ public abstract class TextUI extends ComponentUI * should be in the same coordinate system as the mouse * events. * @return the offset from the start of the document >= 0 + * + * @deprecated replaced by + * {@link #viewToModel2D(JTextComponent, Point2D, Position.Bias[])} */ + @Deprecated(since = "9") public abstract int viewToModel(JTextComponent t, Point pt); /** @@ -110,7 +122,11 @@ public abstract class TextUI extends ComponentUI * * @return the location within the model that best represents the * given point in the view >= 0 + * + * @deprecated replaced by + * {@link #viewToModel2D(JTextComponent, Point2D, Position.Bias[])} */ + @Deprecated(since = "9") public abstract int viewToModel(JTextComponent t, Point pt, Position.Bias[] biasReturn); @@ -222,7 +238,11 @@ public abstract class TextUI extends ComponentUI * @return a {@code String} containing the tooltip * @see javax.swing.text.JTextComponent#getToolTipText * @since 1.4 + * + * @deprecated replaced by + * {@link #getToolTipText2D(JTextComponent, Point2D)} */ + @Deprecated(since = "9") public String getToolTipText(JTextComponent t, Point pt) { return null; } diff --git a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTextUI.java b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTextUI.java index 64894fbb2b1..70020a002db 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTextUI.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicTextUI.java @@ -28,6 +28,8 @@ import java.util.*; import java.awt.*; import java.awt.event.*; import java.awt.datatransfer.*; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.awt.im.InputContext; import java.beans.*; import java.io.*; @@ -1047,7 +1049,12 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory { * @exception BadLocationException if the given position does not * represent a valid location in the associated document * @see TextUI#modelToView + * + * @deprecated replaced by + * {@link #modelToView2D(JTextComponent, int, Position.Bias)} */ + @Deprecated(since = "9") + @Override public Rectangle modelToView(JTextComponent tc, int pos) throws BadLocationException { return modelToView(tc, pos, Position.Bias.Forward); } @@ -1064,8 +1071,30 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory { * @exception BadLocationException if the given position does not * represent a valid location in the associated document * @see TextUI#modelToView + * + * @deprecated replaced by + * {@link #modelToView2D(JTextComponent, int, Position.Bias)} */ - public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) throws BadLocationException { + @Deprecated(since = "9") + @Override + public Rectangle modelToView(JTextComponent tc, int pos, Position.Bias bias) + throws BadLocationException + { + return (Rectangle) modelToView(tc, pos, bias, false); + } + + @Override + public Rectangle2D modelToView2D(JTextComponent tc, int pos, + Position.Bias bias) + throws BadLocationException + { + return modelToView(tc, pos, bias, true); + } + + private Rectangle2D modelToView(JTextComponent tc, int pos, + Position.Bias bias, boolean useFPAPI) + throws BadLocationException + { Document doc = editor.getDocument(); if (doc instanceof AbstractDocument) { ((AbstractDocument)doc).readLock(); @@ -1076,7 +1105,7 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory { rootView.setSize(alloc.width, alloc.height); Shape s = rootView.modelToView(pos, alloc, bias); if (s != null) { - return s.getBounds(); + return useFPAPI ? s.getBounds2D() : s.getBounds(); } } } finally { @@ -1099,7 +1128,12 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory { * @return the offset from the start of the document >= 0, * -1 if not painted * @see TextUI#viewToModel + * + * @deprecated replaced by + * {@link #viewToModel2D(JTextComponent, Point2D, Position.Bias[])} */ + @Deprecated(since = "9") + @Override public int viewToModel(JTextComponent tc, Point pt) { return viewToModel(tc, pt, discardBias); } @@ -1116,9 +1150,25 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory { * @return the offset from the start of the document >= 0, * -1 if the component doesn't yet have a positive size. * @see TextUI#viewToModel + * + * @deprecated replaced by + * {@link #viewToModel2D(JTextComponent, Point2D, Position.Bias[])} */ + @Deprecated(since = "9") + @Override public int viewToModel(JTextComponent tc, Point pt, Position.Bias[] biasReturn) { + return viewToModel(tc, pt.x, pt.y, biasReturn); + } + + @Override + public int viewToModel2D(JTextComponent tc, Point2D pt, + Position.Bias[] biasReturn) { + return viewToModel(tc, (float) pt.getX(), (float) pt.getY(), biasReturn); + } + + private int viewToModel(JTextComponent tc, float x, float y, + Position.Bias[] biasReturn) { int offs = -1; Document doc = editor.getDocument(); if (doc instanceof AbstractDocument) { @@ -1128,7 +1178,7 @@ public abstract class BasicTextUI extends TextUI implements ViewFactory { Rectangle alloc = getVisibleEditorRect(); if (alloc != null) { rootView.setSize(alloc.width, alloc.height); - offs = rootView.viewToModel(pt.x, pt.y, alloc, biasReturn); + offs = rootView.viewToModel(x, y, alloc, biasReturn); } } finally { if (doc instanceof AbstractDocument) { diff --git a/jdk/src/java.desktop/share/classes/javax/swing/plaf/multi/MultiTextUI.java b/jdk/src/java.desktop/share/classes/javax/swing/plaf/multi/MultiTextUI.java index f68984260a4..df57f0bbecc 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/plaf/multi/MultiTextUI.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/plaf/multi/MultiTextUI.java @@ -38,6 +38,8 @@ import javax.swing.plaf.ComponentUI; import javax.swing.JComponent; import java.awt.Graphics; import java.awt.Dimension; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import javax.accessibility.Accessible; /** @@ -97,7 +99,11 @@ public class MultiTextUI extends TextUI { * * @return the value obtained from the first UI, which is * the UI obtained from the default LookAndFeel + * + * @deprecated replaced by + * {@link #modelToView2D(JTextComponent, int, Position.Bias)} */ + @Deprecated(since = "9") public Rectangle modelToView(JTextComponent a, int b) throws BadLocationException { Rectangle returnValue = @@ -113,7 +119,12 @@ public class MultiTextUI extends TextUI { * * @return the value obtained from the first UI, which is * the UI obtained from the default LookAndFeel + * + * @deprecated replaced by + * {@link #modelToView2D(JTextComponent, int, Position.Bias)} */ + @Deprecated(since = "9") + @Override public Rectangle modelToView(JTextComponent a, int b, Position.Bias c) throws BadLocationException { Rectangle returnValue = @@ -124,12 +135,24 @@ public class MultiTextUI extends TextUI { return returnValue; } + @Override + public Rectangle2D modelToView2D(JTextComponent a, int b, Position.Bias c) throws BadLocationException { + Rectangle2D returnValue = + ((TextUI) (uis.elementAt(0))).modelToView2D(a,b,c); + for (int i = 1; i < uis.size(); i++) { + ((TextUI) (uis.elementAt(i))).modelToView2D(a,b,c); + } + return returnValue; + } + /** * Invokes the viewToModel method on each UI handled by this object. * * @return the value obtained from the first UI, which is * the UI obtained from the default LookAndFeel */ + @Deprecated(since = "9") + @Override public int viewToModel(JTextComponent a, Point b) { int returnValue = ((TextUI) (uis.elementAt(0))).viewToModel(a,b); @@ -145,6 +168,8 @@ public class MultiTextUI extends TextUI { * @return the value obtained from the first UI, which is * the UI obtained from the default LookAndFeel */ + @Deprecated(since = "9") + @Override public int viewToModel(JTextComponent a, Point b, Position.Bias[] c) { int returnValue = ((TextUI) (uis.elementAt(0))).viewToModel(a,b,c); @@ -154,6 +179,16 @@ public class MultiTextUI extends TextUI { return returnValue; } + @Override + public int viewToModel2D(JTextComponent a, Point2D b, Position.Bias[] c) { + int returnValue = + ((TextUI) (uis.elementAt(0))).viewToModel2D(a,b,c); + for (int i = 1; i < uis.size(); i++) { + ((TextUI) (uis.elementAt(i))).viewToModel2D(a,b,c); + } + return returnValue; + } + /** * Invokes the getNextVisualPositionFrom method on each UI handled by this object. * diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter1.java b/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter1.java index c68aa094938..f1326cda127 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter1.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter1.java @@ -98,26 +98,27 @@ class GlyphPainter1 extends GlyphView.GlyphPainter { Rectangle alloc = (a instanceof Rectangle) ? (Rectangle)a : a.getBounds(); // determine the x coordinate to render the glyphs - int x = alloc.x; + float x = alloc.x; int p = v.getStartOffset(); int[] justificationData = getJustificationData(v); if (p != p0) { text = v.getText(p, p0); - int width = Utilities.getTabbedTextWidth(v, text, metrics, x, expander, p, - justificationData); + float width = Utilities.getTabbedTextWidth(v, text, metrics, x, + expander, p, + justificationData); x += width; SegmentCache.releaseSharedSegment(text); } // determine the y coordinate to render the glyphs - int y = alloc.y + metrics.getHeight() - metrics.getDescent(); + float y = alloc.y + metrics.getHeight() - metrics.getDescent(); // render the glyphs text = v.getText(p0, p1); g.setFont(metrics.getFont()); Utilities.drawTabbedText(v, text, x, y, g, expander,p0, - justificationData); + justificationData, true); SegmentCache.releaseSharedSegment(text); } @@ -210,9 +211,9 @@ class GlyphPainter1 extends GlyphView.GlyphPainter { TabExpander expander = v.getTabExpander(); Segment s = v.getText(p0, v.getEndOffset()); int[] justificationData = getJustificationData(v); - int index = Utilities.getTabbedTextOffset(v, s, metrics, (int)x, (int)(x+len), + int index = Utilities.getTabbedTextOffset(v, s, metrics, x, (x+len), expander, p0, false, - justificationData); + justificationData, true); SegmentCache.releaseSharedSegment(s); int p1 = p0 + index; return p1; diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter2.java b/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter2.java index 17606d052e4..74e5b3eef1b 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter2.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/GlyphPainter2.java @@ -145,8 +145,9 @@ class GlyphPainter2 extends GlyphView.GlyphPainter { // vertical at the baseline, should use slope and check if glyphs // are being rendered vertically. - alloc.setRect(alloc.getX() + locs[0], alloc.getY(), 1, alloc.getHeight()); - return alloc; + Rectangle2D rect = new Rectangle2D.Float(); + rect.setRect(alloc.getX() + locs[0], alloc.getY(), 1, alloc.getHeight()); + return rect; } /** diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/JTextComponent.java b/jdk/src/java.desktop/share/classes/javax/swing/text/JTextComponent.java index ff28193c3e8..4e06e866dcf 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/JTextComponent.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/JTextComponent.java @@ -49,6 +49,8 @@ import java.awt.im.InputContext; import java.awt.im.InputMethodRequests; import java.awt.font.TextHitInfo; import java.awt.font.TextAttribute; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; import java.awt.print.Printable; import java.awt.print.PrinterException; @@ -1370,11 +1372,37 @@ public abstract class JTextComponent extends JComponent implements Scrollable, A * @exception BadLocationException if the given position does not * represent a valid location in the associated document * @see TextUI#modelToView + * + * @deprecated replaced by + * {@link #modelToView2D(int)} */ + @Deprecated(since = "9") public Rectangle modelToView(int pos) throws BadLocationException { return getUI().modelToView(this, pos); } + /** + * Converts the given location in the model to a place in + * the view coordinate system. + * The component must have a positive size for + * this translation to be computed (i.e. layout cannot + * be computed until the component has been sized). The + * component does not have to be visible or painted. + * + * @param pos the position {@code >= 0} + * @return the coordinates as a rectangle, with (r.x, r.y) as the location + * in the coordinate system, or null if the component does + * not yet have a positive size. + * @exception BadLocationException if the given position does not + * represent a valid location in the associated document + * @see TextUI#modelToView2D + * + * @since 9 + */ + public Rectangle2D modelToView2D(int pos) throws BadLocationException { + return getUI().modelToView2D(this, pos, Position.Bias.Forward); + } + /** * Converts the given place in the view coordinate system * to the nearest representative location in the model. @@ -1388,11 +1416,35 @@ public abstract class JTextComponent extends JComponent implements Scrollable, A * or -1 if the component does not yet have a positive * size. * @see TextUI#viewToModel + * + * @deprecated replaced by + * {@link #viewToModel2D(Point2D)} */ + @Deprecated(since = "9") public int viewToModel(Point pt) { return getUI().viewToModel(this, pt); } + /** + * Converts the given place in the view coordinate system + * to the nearest representative location in the model. + * The component must have a positive size for + * this translation to be computed (i.e. layout cannot + * be computed until the component has been sized). The + * component does not have to be visible or painted. + * + * @param pt the location in the view to translate + * @return the offset {@code >= 0} from the start of the document, + * or {@code -1} if the component does not yet have a positive + * size. + * @see TextUI#viewToModel2D + * + * @since 9 + */ + public int viewToModel2D(Point2D pt) { + return getUI().viewToModel2D(this, pt, new Position.Bias[1]); + } + /** * Transfers the currently selected range in the associated * text model to the system clipboard, removing the contents diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/ParagraphView.java b/jdk/src/java.desktop/share/classes/javax/swing/text/ParagraphView.java index fc96021ccc3..5af2ebd3a3f 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/ParagraphView.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/ParagraphView.java @@ -27,6 +27,7 @@ package javax.swing.text; import java.util.Arrays; import java.awt.*; import java.awt.font.TextAttribute; +import java.awt.geom.Rectangle2D; import javax.swing.event.*; import javax.swing.SizeRequirements; @@ -888,10 +889,9 @@ public class ParagraphView extends FlowView implements TabExpander { int height = r.height; int y = r.y; Shape loc = super.modelToView(pos, a, b); - r = loc.getBounds(); - r.height = height; - r.y = y; - return r; + Rectangle2D bounds = loc.getBounds2D(); + bounds.setRect(bounds.getX(), y, bounds.getWidth(), height); + return bounds; } /** diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/PasswordView.java b/jdk/src/java.desktop/share/classes/javax/swing/text/PasswordView.java index c5a4fed9146..dc65285e75c 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/PasswordView.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/PasswordView.java @@ -26,7 +26,11 @@ package javax.swing.text; import sun.swing.SwingUtilities2; import java.awt.*; +import java.awt.font.FontRenderContext; +import java.security.AccessController; +import java.security.PrivilegedAction; import javax.swing.JPasswordField; +import static javax.swing.text.PlainView.isFPMethodOverriden; /** * Implements a View suitable for use in JPasswordField @@ -61,15 +65,40 @@ public class PasswordView extends FieldView { * @param p1 the ending offset in the model >= p0 * @return the X location of the end of the range >= 0 * @exception BadLocationException if p0 or p1 are out of range + * + * @deprecated replaced by + * {@link #drawUnselectedText(Graphics2D, float, float, int, int)} */ + @Deprecated(since = "9") + @Override protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { + return (int) drawUnselectedTextImpl(g, x, y, p0, p1, false); + } + @Override + protected float drawUnselectedText(Graphics2D g, float x, float y, + int p0, int p1) + throws BadLocationException + { + return drawUnselectedTextImpl(g, x, y, p0, p1, true); + } + + private float drawUnselectedTextImpl(Graphics g, float x, float y, + int p0, int p1, + boolean useFPAPI) + throws BadLocationException + { Container c = getContainer(); if (c instanceof JPasswordField) { JPasswordField f = (JPasswordField) c; - if (! f.echoCharIsSet()) { - return super.drawUnselectedText(g, x, y, p0, p1); + if (!f.echoCharIsSet()) { + boolean useDrawUnselectedFPAPI = useFPAPI + && drawUnselectedTextOverridden + && g instanceof Graphics2D; + return (useDrawUnselectedFPAPI ) + ? super.drawUnselectedText((Graphics2D) g, x, y, p0, p1) + : super.drawUnselectedText(g, (int) x, (int) y, p0, p1); } if (f.isEnabled()) { g.setColor(f.getForeground()); @@ -79,8 +108,13 @@ public class PasswordView extends FieldView { } char echoChar = f.getEchoChar(); int n = p1 - p0; + boolean useEchoCharFPAPI = useFPAPI + && drawEchoCharacterOverridden + && g instanceof Graphics2D; for (int i = 0; i < n; i++) { - x = drawEchoCharacter(g, x, y, echoChar); + x = (useEchoCharFPAPI) + ? drawEchoCharacter((Graphics2D) g, x, y, echoChar) + : drawEchoCharacter(g, (int) x, (int) y, echoChar); } } return x; @@ -100,20 +134,50 @@ public class PasswordView extends FieldView { * @param p1 the ending offset in the model >= p0 * @return the X location of the end of the range >= 0 * @exception BadLocationException if p0 or p1 are out of range + * + * @deprecated replaced by + * {@link #drawSelectedText(Graphics2D, float, float, int, int)} */ + @Deprecated(since = "9") + @Override protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { + return (int) drawSelectedTextImpl(g, x, y, p0, p1, false); + } + + @Override + protected float drawSelectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException + { + return drawSelectedTextImpl(g, x, y, p0, p1, true); + } + + private float drawSelectedTextImpl(Graphics g, float x, float y, + int p0, int p1, + boolean useFPAPI) + throws BadLocationException { g.setColor(selected); Container c = getContainer(); if (c instanceof JPasswordField) { JPasswordField f = (JPasswordField) c; - if (! f.echoCharIsSet()) { - return super.drawSelectedText(g, x, y, p0, p1); + if (!f.echoCharIsSet()) { + boolean useDrawUnselectedFPAPI = useFPAPI + && drawSelectedTextOverridden + && g instanceof Graphics2D; + return (useFPAPI) + ? super.drawSelectedText((Graphics2D) g, x, y, p0, p1) + : super.drawSelectedText(g, (int) x, (int) y, p0, p1); } char echoChar = f.getEchoChar(); int n = p1 - p0; + boolean useEchoCharFPAPI = useFPAPI + && drawEchoCharacterOverridden + && g instanceof Graphics2D; for (int i = 0; i < n; i++) { - x = drawEchoCharacter(g, x, y, echoChar); + x = (useEchoCharFPAPI) + ? drawEchoCharacter((Graphics2D) g, x, y, echoChar) + : drawEchoCharacter(g, (int) x, (int) y, echoChar); + } } return x; @@ -130,12 +194,13 @@ public class PasswordView extends FieldView { * @param y the starting Y coordinate >= 0 * @param c the echo character * @return the updated X position >= 0 + * + * @deprecated replaced by + * {@link #drawEchoCharacter(Graphics2D, float, float, char)} */ + @Deprecated(since = "9") protected int drawEchoCharacter(Graphics g, int x, int y, char c) { - ONE[0] = c; - SwingUtilities2.drawChars(Utilities.getJComponent(this), - g, ONE, 0, 1, x, y); - return x + g.getFontMetrics().charWidth(c); + return (int) drawEchoCharacterImpl(g, x, y, c, false); } /** @@ -144,18 +209,29 @@ public class PasswordView extends FieldView { * object is set to the appropriate foreground color for selected * or unselected text. * - * @implSpec This implementation calls - * {@link #drawEchoCharacter(Graphics, int, int, char) - * drawEchoCharacter((Graphics) g, (int) x, (int) y, c)}. - * * @param g the graphics context * @param x the starting X coordinate {@code >= 0} * @param y the starting Y coordinate {@code >= 0} * @param c the echo character * @return the updated X position {@code >= 0} + * + * @since 9 */ protected float drawEchoCharacter(Graphics2D g, float x, float y, char c) { - return drawEchoCharacter((Graphics) g, (int) x, (int) y, c); + return drawEchoCharacterImpl(g, x, y, c, true); + } + + private float drawEchoCharacterImpl(Graphics g, float x, float y, + char c, boolean useFPAPI) { + ONE[0] = c; + SwingUtilities2.drawChars(Utilities.getJComponent(this), + g, ONE, 0, 1, x, y); + if (useFPAPI) { + return x + g.getFontMetrics().charWidth(c); + } else { + FontRenderContext frc = g.getFontMetrics().getFontRenderContext(); + return x + (float) g.getFont().getStringBounds(ONE, 0, 1, frc).getWidth(); + } } /** @@ -253,4 +329,23 @@ public class PasswordView extends FieldView { } static char[] ONE = new char[1]; + + private final boolean drawEchoCharacterOverridden; + + { + final Class CLS = getClass(); + final Class INT = Integer.TYPE; + final Class FP = Float.TYPE; + final Class CHAR = Character.TYPE; + + drawEchoCharacterOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {Graphics.class, INT, INT, CHAR}; + Class[] fpTypes = {Graphics2D.class, FP, FP, CHAR}; + return isFPMethodOverriden("drawEchoCharacter", CLS, intTypes, fpTypes); + } + }); + } } diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/PlainView.java b/jdk/src/java.desktop/share/classes/javax/swing/text/PlainView.java index a4efd018508..8675436f610 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/PlainView.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/PlainView.java @@ -24,11 +24,14 @@ */ package javax.swing.text; -import java.util.Vector; -import java.util.Properties; import java.awt.*; +import java.awt.font.FontRenderContext; +import java.awt.geom.Rectangle2D; +import java.security.AccessController; +import java.security.PrivilegedAction; import java.util.Objects; import javax.swing.event.*; +import java.lang.reflect.Module; /** * Implements View interface for a simple multi-line text view @@ -60,17 +63,6 @@ public class PlainView extends View implements TabExpander { return size; } - /** - * Returns the tab size set for the document, defaulting to 8. - * - * @implSpec This implementation calls {@link #getTabSize() getTabSize()}. - * - * @return the tab size - */ - protected float getFractionalTabSize() { - return getTabSize(); - } - /** * Renders a line of text, suppressing whitespace at the end * and expanding any tabs. This is implemented to make calls @@ -84,8 +76,16 @@ public class PlainView extends View implements TabExpander { * @param y the starting Y position >= 0 * @see #drawUnselectedText * @see #drawSelectedText + * + * @deprecated replaced by + * {@link #drawLine(int, Graphics2D, float, float)} */ + @Deprecated(since = "9") protected void drawLine(int lineIndex, Graphics g, int x, int y) { + drawLineImpl(lineIndex, g, x, y); + } + + private void drawLineImpl(int lineIndex, Graphics g, float x, float y) { Element line = getElement().getElement(lineIndex); Element elem; @@ -112,22 +112,23 @@ public class PlainView extends View implements TabExpander { * {@code drawSelectedText} so that the way selected and * unselected text are rendered can be customized. * - * @implSpec This implementation calls - * {@link #drawLine(int, Graphics, int, int) - * drawLine(lineIndex, (Graphics)g, (int) x, (int) y)}. - * * @param lineIndex the line to draw {@code >= 0} * @param g the {@code Graphics} context * @param x the starting X position {@code >= 0} * @param y the starting Y position {@code >= 0} * @see #drawUnselectedText * @see #drawSelectedText + * + * @since 9 */ protected void drawLine(int lineIndex, Graphics2D g, float x, float y) { - drawLine(lineIndex, (Graphics)g, (int) x, (int) y); + drawLineImpl(lineIndex, g, x, y); } - private int drawElement(int lineIndex, Element elem, Graphics g, int x, int y) throws BadLocationException { + private float drawElement(int lineIndex, Element elem, Graphics g, + float x, float y) + throws BadLocationException + { int p0 = elem.getStartOffset(); int p1 = elem.getEndOffset(); p1 = Math.min(getDocument().getLength(), p1); @@ -144,23 +145,23 @@ public class PlainView extends View implements TabExpander { } else { if (sel0 == sel1 || selected == unselected) { // no selection, or it is invisible - x = drawUnselectedText(g, x, y, p0, p1); + x = callDrawUnselectedText(g, x, y, p0, p1); } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) { - x = drawSelectedText(g, x, y, p0, p1); + x = callDrawSelectedText(g, x, y, p0, p1); } else if (sel0 >= p0 && sel0 <= p1) { if (sel1 >= p0 && sel1 <= p1) { - x = drawUnselectedText(g, x, y, p0, sel0); - x = drawSelectedText(g, x, y, sel0, sel1); - x = drawUnselectedText(g, x, y, sel1, p1); + x = callDrawUnselectedText(g, x, y, p0, sel0); + x = callDrawSelectedText(g, x, y, sel0, sel1); + x = callDrawUnselectedText(g, x, y, sel1, p1); } else { - x = drawUnselectedText(g, x, y, p0, sel0); - x = drawSelectedText(g, x, y, sel0, p1); + x = callDrawUnselectedText(g, x, y, p0, sel0); + x = callDrawSelectedText(g, x, y, sel0, p1); } } else if (sel1 >= p0 && sel1 <= p1) { - x = drawSelectedText(g, x, y, p0, sel1); - x = drawUnselectedText(g, x, y, sel1, p1); + x = callDrawSelectedText(g, x, y, p0, sel1); + x = callDrawUnselectedText(g, x, y, sel1, p1); } else { - x = drawUnselectedText(g, x, y, p0, p1); + x = callDrawUnselectedText(g, x, y, p0, p1); } } @@ -178,14 +179,36 @@ public class PlainView extends View implements TabExpander { * @param p1 the ending position in the model >= 0 * @return the X location of the end of the range >= 0 * @exception BadLocationException if the range is invalid + * + * @deprecated replaced by + * {@link #drawUnselectedText(Graphics2D, float, float, int, int)} */ + @Deprecated(since = "9") protected int drawUnselectedText(Graphics g, int x, int y, int p0, int p1) throws BadLocationException { + return (int) drawUnselectedTextImpl(g, x, y, p0, p1, false); + } + + private float callDrawUnselectedText(Graphics g, float x, float y, + int p0, int p1) + throws BadLocationException + { + return drawUnselectedTextOverridden && (g instanceof Graphics2D) + ? drawUnselectedText((Graphics2D) g, x, y, p0, p1) + : drawUnselectedText(g, (int) x, (int) y, p0, p1); + } + + private float drawUnselectedTextImpl(Graphics g, float x, float y, + int p0, int p1, + boolean useFPAPI) + throws BadLocationException + { g.setColor(unselected); Document doc = getDocument(); Segment s = SegmentCache.getSharedSegment(); doc.getText(p0, p1 - p0, s); - int ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0); + float ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0, null, + useFPAPI); SegmentCache.releaseSharedSegment(s); return ret; } @@ -194,10 +217,6 @@ public class PlainView extends View implements TabExpander { * Renders the given range in the model as normal unselected * text. Uses the foreground or disabled color to render the text. * - * @implSpec This implementation calls - * {@link #drawUnselectedText(Graphics, int, int, int, int) - * drawUnselectedText((Graphics)g, (int) x, (int) y, p0, p1)}. - * * @param g the graphics context * @param x the starting X coordinate {@code >= 0} * @param y the starting Y coordinate {@code >= 0} @@ -205,10 +224,12 @@ public class PlainView extends View implements TabExpander { * @param p1 the ending position in the model {@code >= 0} * @return the X location of the end of the range {@code >= 0} * @exception BadLocationException if the range is invalid + * + * @since 9 */ protected float drawUnselectedText(Graphics2D g, float x, float y, int p0, int p1) throws BadLocationException { - return drawUnselectedText((Graphics)g, (int) x, (int) y, p0, p1); + return drawUnselectedTextImpl(g, x, y, p0, p1, true); } /** @@ -224,14 +245,38 @@ public class PlainView extends View implements TabExpander { * @param p1 the ending position in the model >= 0 * @return the location of the end of the range * @exception BadLocationException if the range is invalid + * + * @deprecated replaced by + * {@link #drawSelectedText(Graphics2D, float, float, int, int)} */ + @Deprecated(since = "9") protected int drawSelectedText(Graphics g, int x, - int y, int p0, int p1) throws BadLocationException { + int y, int p0, int p1) + throws BadLocationException + { + return (int) drawSelectedTextImpl(g, x, y, p0, p1, false); + } + + float callDrawSelectedText(Graphics g, float x, float y, + int p0, int p1) + throws BadLocationException + { + return drawSelectedTextOverridden && g instanceof Graphics2D + ? drawSelectedText((Graphics2D) g, x, y, p0, p1) + : drawSelectedText(g, (int) x, (int) y, p0, p1); + } + + private float drawSelectedTextImpl(Graphics g, float x, float y, + int p0, int p1, + boolean useFPAPI) + throws BadLocationException + { g.setColor(selected); Document doc = getDocument(); Segment s = SegmentCache.getSharedSegment(); doc.getText(p0, p1 - p0, s); - int ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0); + float ret = Utilities.drawTabbedText(this, s, x, y, g, this, p0, null, + useFPAPI); SegmentCache.releaseSharedSegment(s); return ret; } @@ -242,10 +287,6 @@ public class PlainView extends View implements TabExpander { * the hosting component. It assumes the highlighter will render * the selected background. * - * @implSpec This implementation calls - * {@link #drawSelectedText(Graphics, int, int, int, int) - * drawSelectedText((Graphics)g, (int) x, (int) y, p0, p1)}. - * * @param g the graphics context * @param x the starting X coordinate {@code >= 0} * @param y the starting Y coordinate {@code >= 0} @@ -253,11 +294,12 @@ public class PlainView extends View implements TabExpander { * @param p1 the ending position in the model {@code >= 0} * @return the location of the end of the range * @exception BadLocationException if the range is invalid + * + * @since 9 */ - protected float drawSelectedText(Graphics2D g, float x, float y, int p0, int p1) throws BadLocationException { - return drawSelectedText((Graphics)g, (int) x, (int) y, p0, p1); + return drawSelectedTextImpl(g, x, y, p0, p1, true); } /** @@ -287,7 +329,13 @@ public class PlainView extends View implements TabExpander { // The font changed, we need to recalculate the // longest line. calculateLongestLine(); - tabSize = getTabSize() * metrics.charWidth('m'); + if (useFloatingPointAPI) { + FontRenderContext frc = metrics.getFontRenderContext(); + float tabWidth = (float) font.getStringBounds("m", frc).getWidth(); + tabSize = getTabSize() * tabWidth; + } else { + tabSize = getTabSize() * metrics.charWidth('m'); + } } } @@ -388,7 +436,11 @@ public class PlainView extends View implements TabExpander { originalA, host, this); } } - drawLine(line, g, x, y); + if (drawLineOverridden && (g instanceof Graphics2D)) { + drawLine(line, (Graphics2D) g, (float) x, (float) y); + } else { + drawLine(line, g, x, y); + } y += fontHeight; if (line == 0) { // This should never really happen, in so far as if @@ -435,6 +487,13 @@ public class PlainView extends View implements TabExpander { int p0 = line.getStartOffset(); Segment s = SegmentCache.getSharedSegment(); doc.getText(p0, pos - p0, s); + + if (useFloatingPointAPI) { + float xOffs = Utilities.getTabbedTextWidth(s, metrics, (float) tabBase, this, p0); + SegmentCache.releaseSharedSegment(s); + return new Rectangle2D.Float(lineArea.x + xOffs, lineArea.y, 1, metrics.getHeight()); + } + int xOffs = Utilities.getTabbedTextWidth(s, metrics, tabBase, this,p0); SegmentCache.releaseSharedSegment(s); @@ -456,14 +515,13 @@ public class PlainView extends View implements TabExpander { * given point in the view >= 0 * @see View#viewToModel */ - public int viewToModel(float fx, float fy, Shape a, Position.Bias[] bias) { + public int viewToModel(float x, float y, Shape a, Position.Bias[] bias) { // PENDING(prinz) properly calculate bias bias[0] = Position.Bias.Forward; Rectangle alloc = a.getBounds(); Document doc = getDocument(); - int x = (int) fx; - int y = (int) fy; + if (y < alloc.y) { // above the area covered by this icon, so the position // is assumed to be the start of the coverage for this view. @@ -481,7 +539,7 @@ public class PlainView extends View implements TabExpander { Element map = doc.getDefaultRootElement(); int fontHeight = metrics.getHeight(); int lineIndex = (fontHeight > 0 ? - Math.abs((y - alloc.y) / fontHeight) : + (int)Math.abs((y - alloc.y) / fontHeight) : map.getElementCount() - 1); if (lineIndex >= map.getElementCount()) { return getEndOffset() - 1; @@ -507,7 +565,7 @@ public class PlainView extends View implements TabExpander { doc.getText(p0, p1 - p0, s); tabBase = alloc.x; int offs = p0 + Utilities.getTabbedTextOffset(s, metrics, - tabBase, x, this, p0); + tabBase, x, this, p0, true); SegmentCache.releaseSharedSegment(s); return offs; } catch (BadLocationException e) { @@ -586,7 +644,7 @@ public class PlainView extends View implements TabExpander { if (tabSize == 0) { return x; } - int ntabs = (((int) x) - tabBase) / tabSize; + float ntabs = (x - tabBase) / tabSize; return tabBase + ((ntabs + 1) * tabSize); } @@ -758,6 +816,28 @@ public class PlainView extends View implements TabExpander { return w; } + static boolean isFPMethodOverriden(String method, + Class cls, + Class[] intTypes, + Class[] fpTypes) + { + Module thisModule = PlainView.class.getModule(); + while (!thisModule.equals(cls.getModule())) { + try { + cls.getDeclaredMethod(method, fpTypes); + return true; + } catch (Exception e1) { + try { + cls.getDeclaredMethod(method, intTypes); + return false; + } catch (Exception e2) { + cls = cls.getSuperclass(); + } + } + } + return true; + } + // --- member variables ----------------------------------------------- /** @@ -780,7 +860,7 @@ public class PlainView extends View implements TabExpander { Font font; Segment lineBuffer; - int tabSize; + float tabSize; int tabBase; int sel0; @@ -796,4 +876,46 @@ public class PlainView extends View implements TabExpander { */ int firstLineOffset; + final boolean drawLineOverridden; + final boolean drawSelectedTextOverridden; + final boolean drawUnselectedTextOverridden; + final boolean useFloatingPointAPI; + + { + final Class CLS = getClass(); + final Class INT = Integer.TYPE; + final Class FP = Float.TYPE; + + drawLineOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {INT, Graphics.class, INT, INT}; + Class[] fpTypes = {INT, Graphics2D.class, FP, FP}; + return isFPMethodOverriden("drawLine", CLS, intTypes, fpTypes); + } + }); + + drawUnselectedTextOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {Graphics.class, INT, INT, INT, INT}; + Class[] fpTypes = {Graphics2D.class, FP, FP, INT, INT}; + return isFPMethodOverriden("drawUnselectedText", CLS, intTypes, fpTypes); + } + }); + + drawSelectedTextOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {Graphics.class, INT, INT, INT, INT}; + Class[] fpTypes = {Graphics2D.class, FP, FP, INT, INT}; + return isFPMethodOverriden("drawSelectedText", CLS, intTypes, fpTypes); + } + }); + + useFloatingPointAPI = drawUnselectedTextOverridden || drawSelectedTextOverridden; + } } diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java b/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java index bf26227ed87..940abb47c2c 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/Utilities.java @@ -24,24 +24,23 @@ */ package javax.swing.text; -import java.lang.reflect.Method; - import java.awt.Component; import java.awt.Rectangle; import java.awt.Graphics; import java.awt.FontMetrics; import java.awt.Shape; -import java.awt.Toolkit; import java.awt.Graphics2D; -import java.awt.font.FontRenderContext; -import java.awt.font.TextLayout; import java.awt.font.TextAttribute; +import java.awt.geom.Rectangle2D; import java.text.*; import javax.swing.JComponent; import javax.swing.SwingConstants; import javax.swing.text.ParagraphView.Row; import sun.swing.SwingUtilities2; +import static sun.swing.SwingUtilities2.drawChars; +import static sun.swing.SwingUtilities2.getFontCharWidth; +import static sun.swing.SwingUtilities2.getFontCharsWidth; /** * A collection of methods to deal with various text @@ -78,7 +77,11 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset of the text in the document >= 0 * @return the X location at the end of the rendered text + * + * @deprecated replaced by + * {@link #drawTabbedText(Segment, float, float, Graphics2D, TabExpander, int)} */ + @Deprecated(since = "9") public static final int drawTabbedText(Segment s, int x, int y, Graphics g, TabExpander e, int startOffset) { return drawTabbedText(null, s, x, y, g, e, startOffset); @@ -96,6 +99,8 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset of the text in the document {@code >= 0} * @return the X location at the end of the rendered text + * + * @since 9 */ public static final float drawTabbedText(Segment s, float x, float y, Graphics2D g, @@ -138,9 +143,19 @@ public class Utilities { Segment s, int x, int y, Graphics g, TabExpander e, int startOffset, int [] justificationData) { + return (int) drawTabbedText(view, s, x, y, g, e, startOffset, + justificationData, false); + } + + static final float drawTabbedText(View view, + Segment s, float x, float y, Graphics g, + TabExpander e, int startOffset, + int [] justificationData, + boolean useFPAPI) + { JComponent component = getJComponent(view); FontMetrics metrics = SwingUtilities2.getFontMetrics(component, g); - int nextX = x; + float nextX = x; char[] txt = s.array; int txtOffset = s.offset; int flushLen = 0; @@ -174,19 +189,19 @@ public class Utilities { && i <= endJustifiableContent )) { if (flushLen > 0) { - nextX = SwingUtilities2.drawChars(component, g, txt, - flushIndex, flushLen, x, y); + nextX = drawChars(component, g, txt, flushIndex, flushLen, x, y); flushLen = 0; } flushIndex = i + 1; if (txt[i] == '\t') { if (e != null) { - nextX = (int) e.nextTabStop((float) nextX, startOffset + i - txtOffset); + nextX = e.nextTabStop(nextX, startOffset + i - txtOffset); } else { - nextX += metrics.charWidth(' '); + nextX += getFontCharWidth(' ', metrics, useFPAPI); } } else if (txt[i] == ' ') { - nextX += metrics.charWidth(' ') + spaceAddon; + float spaceWidth = getFontCharWidth(' ', metrics, useFPAPI); + nextX += spaceWidth + spaceAddon; if (i <= spaceAddonLeftoverEnd) { nextX++; } @@ -194,8 +209,8 @@ public class Utilities { x = nextX; } else if ((txt[i] == '\n') || (txt[i] == '\r')) { if (flushLen > 0) { - nextX = SwingUtilities2.drawChars(component, g, txt, - flushIndex, flushLen, x, y); + nextX = drawChars(component, g, txt, flushIndex, flushLen, + x, y, useFPAPI); flushLen = 0; } flushIndex = i + 1; @@ -205,8 +220,7 @@ public class Utilities { } } if (flushLen > 0) { - nextX = SwingUtilities2.drawChars(component, g,txt, flushIndex, - flushLen, x, y); + nextX = drawChars(component, g,txt, flushIndex, flushLen, x, y, useFPAPI); } return nextX; } @@ -223,7 +237,11 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset of the text in the document >= 0 * @return the width of the text + * + * @deprecated replaced by + * {@link #getTabbedTextWidth(Segment, FontMetrics, float, TabExpander, int)} */ + @Deprecated(since = "9") public static final int getTabbedTextWidth(Segment s, FontMetrics metrics, int x, TabExpander e, int startOffset) { return getTabbedTextWidth(null, s, metrics, x, e, startOffset, null); @@ -240,11 +258,13 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset of the text in the document {@code >= 0} * @return the width of the text + * + * @since 9 */ public static final float getTabbedTextWidth(Segment s, FontMetrics metrics, float x, TabExpander e, int startOffset) { - return getTabbedTextWidth(s, metrics, (int) x, e, startOffset); + return getTabbedTextWidth(null, s, metrics, x, e, startOffset, null); } // In addition to the previous method it can extend spaces for @@ -254,10 +274,32 @@ public class Utilities { // one: // @param justificationData justificationData for the row. // if null not justification is needed - static final int getTabbedTextWidth(View view, Segment s, FontMetrics metrics, int x, + static final int getTabbedTextWidth(View view, Segment s, + FontMetrics metrics, int x, TabExpander e, int startOffset, - int[] justificationData) { - int nextX = x; + int[] justificationData) + { + return (int) getTabbedTextWidth(view, s, metrics, x, e, startOffset, + justificationData, false); + + } + + static final float getTabbedTextWidth(View view, Segment s, + FontMetrics metrics, float x, + TabExpander e, int startOffset, + int[] justificationData) + { + return getTabbedTextWidth(view, s, metrics, x, e, startOffset, + justificationData, true); + + } + + static final float getTabbedTextWidth(View view, Segment s, + FontMetrics metrics, float x, + TabExpander e, int startOffset, + int[] justificationData, + boolean useFPAPI) { + float nextX = x; char[] txt = s.array; int txtOffset = s.offset; int n = s.offset + s.count; @@ -294,13 +336,13 @@ public class Utilities { charCount = 0; if (txt[i] == '\t') { if (e != null) { - nextX = (int) e.nextTabStop((float) nextX, - startOffset + i - txtOffset); + nextX = e.nextTabStop(nextX, startOffset + i - txtOffset); } else { - nextX += metrics.charWidth(' '); + nextX += getFontCharWidth(' ', metrics, useFPAPI); } } else if (txt[i] == ' ') { - nextX += metrics.charWidth(' ') + spaceAddon; + float spaceWidth = getFontCharWidth(' ', metrics, useFPAPI); + nextX += spaceWidth + spaceAddon; if (i <= spaceAddonLeftoverEnd) { nextX++; } @@ -308,13 +350,15 @@ public class Utilities { } else if(txt[i] == '\n') { // Ignore newlines, they take up space and we shouldn't be // counting them. - nextX += metrics.charsWidth(txt, i - charCount, charCount); + nextX += getFontCharsWidth(txt, i - charCount, charCount, + metrics, useFPAPI); charCount = 0; } else { charCount++; } } - nextX += metrics.charsWidth(txt, n - charCount, charCount); + nextX += getFontCharsWidth(txt, n - charCount, charCount, + metrics, useFPAPI); return nextX - x; } @@ -334,7 +378,12 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset of the text in the document >= 0 * @return the offset into the text >= 0 + * + * @deprecated replaced by + * {@link #getTabbedTextOffset(Segment, FontMetrics, float, float, + * TabExpander, int, boolean)} */ + @Deprecated(since = "9") public static final int getTabbedTextOffset(Segment s, FontMetrics metrics, int x0, int x, TabExpander e, int startOffset) { @@ -346,7 +395,7 @@ public class Utilities { int startOffset, int[] justificationData) { return getTabbedTextOffset(view, s, metrics, x0, x, e, startOffset, true, - justificationData); + justificationData, false); } /** @@ -365,13 +414,19 @@ public class Utilities { * @param startOffset starting offset of the text in the document >= 0 * @param round whether or not to round * @return the offset into the text >= 0 + * + * @deprecated replaced by + * {@link #getTabbedTextOffset(Segment, FontMetrics, float, float, + * TabExpander, int, boolean)} */ + @Deprecated(since = "9") public static final int getTabbedTextOffset(Segment s, FontMetrics metrics, int x0, int x, TabExpander e, int startOffset, boolean round) { - return getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset, round, null); + return getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset, + round, null, false); } /** @@ -390,6 +445,8 @@ public class Utilities { * @param startOffset starting offset of the text in the document {@code >= 0} * @param round whether or not to round * @return the offset into the text {@code >= 0} + * + * @since 9 */ public static final int getTabbedTextOffset(Segment s, FontMetrics metrics, @@ -398,8 +455,8 @@ public class Utilities { int startOffset, boolean round) { - return getTabbedTextOffset(null, s, metrics, (int) x0, (int) x, e, - startOffset, round, null); + return getTabbedTextOffset(null, s, metrics, x0, x, e, + startOffset, round, null, true); } // In addition to the previous method it can extend spaces for @@ -412,15 +469,16 @@ public class Utilities { static final int getTabbedTextOffset(View view, Segment s, FontMetrics metrics, - int x0, int x, TabExpander e, + float x0, float x, TabExpander e, int startOffset, boolean round, - int[] justificationData) { + int[] justificationData, + boolean useFPAPI) { if (x0 >= x) { // x before x0, return. return 0; } - int nextX = x0; + float nextX = x0; // s may be a shared segment, so it is copied prior to calling // the tab expander char[] txt = s.array; @@ -456,19 +514,19 @@ public class Utilities { )){ if (txt[i] == '\t') { if (e != null) { - nextX = (int) e.nextTabStop((float) nextX, - startOffset + i - txtOffset); + nextX = e.nextTabStop(nextX, startOffset + i - txtOffset); } else { - nextX += metrics.charWidth(' '); + nextX += getFontCharWidth(' ', metrics, useFPAPI); } } else if (txt[i] == ' ') { - nextX += metrics.charWidth(' ') + spaceAddon; + nextX += getFontCharWidth(' ', metrics, useFPAPI); + nextX += spaceAddon; if (i <= spaceAddonLeftoverEnd) { nextX++; } } } else { - nextX += metrics.charWidth(txt[i]); + nextX += getFontCharWidth(txt[i], metrics, useFPAPI); } if (x < nextX) { // found the hit position... return the appropriate side @@ -480,12 +538,15 @@ public class Utilities { if (round) { offset = i + 1 - txtOffset; - int width = metrics.charsWidth(txt, txtOffset, offset); - int span = x - x0; + float width = getFontCharsWidth(txt, txtOffset, offset, + metrics, useFPAPI); + float span = x - x0; if (span < width) { while (offset > 0) { - int nextWidth = offset > 1 ? metrics.charsWidth(txt, txtOffset, offset - 1) : 0; + float charsWidth = getFontCharsWidth(txt, txtOffset, + offset - 1, metrics, useFPAPI); + float nextWidth = offset > 1 ? charsWidth : 0; if (span >= nextWidth) { if (span - nextWidth < width - span) { @@ -502,7 +563,9 @@ public class Utilities { } else { offset = i - txtOffset; - while (offset > 0 && metrics.charsWidth(txt, txtOffset, offset) > (x - x0)) { + while (offset > 0 && getFontCharsWidth(txt, txtOffset, offset, + metrics, useFPAPI) + > (x - x0)) { offset--; } } @@ -528,15 +591,26 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset in the document of the text * @return the offset into the given text + * + * @deprecated replaced by + * {@link #getBreakLocation(Segment, FontMetrics, float, float, + * TabExpander, int)} */ + @Deprecated(since = "9") public static final int getBreakLocation(Segment s, FontMetrics metrics, int x0, int x, TabExpander e, int startOffset) { + return getBreakLocation(s, metrics, x0, x, e, startOffset, false); + } + + static final int getBreakLocation(Segment s, FontMetrics metrics, + float x0, float x, TabExpander e, + int startOffset, boolean useFPIAPI) { char[] txt = s.array; int txtOffset = s.offset; int txtCount = s.count; - int index = Utilities.getTabbedTextOffset(s, metrics, x0, x, - e, startOffset, false); + int index = getTabbedTextOffset(null, s, metrics, x0, x, e, startOffset, + false, null, useFPIAPI); if (index >= txtCount - 1) { return txtCount; @@ -577,11 +651,13 @@ public class Utilities { * tabs will be expanded as a space character. * @param startOffset starting offset in the document of the text * @return the offset into the given text + * + * @since 9 */ public static final int getBreakLocation(Segment s, FontMetrics metrics, float x0, float x, TabExpander e, int startOffset) { - return getBreakLocation(s, metrics, (int) x0, (int) x, e, startOffset); + return getBreakLocation(s, metrics, x0, x, e, startOffset, false); } /** @@ -627,16 +703,16 @@ public class Utilities { * @exception BadLocationException if the offset is out of range */ public static final int getRowEnd(JTextComponent c, int offs) throws BadLocationException { - Rectangle r = c.modelToView(offs); + Rectangle2D r = c.modelToView2D(offs); if (r == null) { return -1; } int n = c.getDocument().getLength(); int lastOffs = offs; - int y = r.y; - while ((r != null) && (y == r.y)) { + double y = r.getY(); + while ((r != null) && (y == r.getY())) { // Skip invisible elements - if (r.height !=0) { + if (r.getHeight() !=0) { offs = lastOffs; } lastOffs += 1; @@ -657,27 +733,44 @@ public class Utilities { * @return the position >= 0 if the request can be computed, otherwise * a value of -1 will be returned. * @exception BadLocationException if the offset is out of range + * + * @deprecated replaced by + * {@link #getPositionAbove(JTextComponent, int, float)} */ - public static final int getPositionAbove(JTextComponent c, int offs, int x) throws BadLocationException { + @Deprecated(since = "9") + public static final int getPositionAbove(JTextComponent c, int offs, int x) + throws BadLocationException + { + return getPositionAbove(c, offs, x, false); + } + + static final int getPositionAbove(JTextComponent c, int offs, float x, + boolean useFPAPI) throws BadLocationException + { int lastOffs = getRowStart(c, offs) - 1; if (lastOffs < 0) { return -1; } - int bestSpan = Integer.MAX_VALUE; - int y = 0; - Rectangle r = null; + double bestSpan = Integer.MAX_VALUE; + double y = 0; + Rectangle2D r = null; if (lastOffs >= 0) { - r = c.modelToView(lastOffs); - y = r.y; + r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs); + y = r.getY(); } - while ((r != null) && (y == r.y)) { - int span = Math.abs(r.x - x); + while ((r != null) && (y == r.getY())) { + double span = Math.abs(r.getX() - x); if (span < bestSpan) { offs = lastOffs; bestSpan = span; } lastOffs -= 1; - r = (lastOffs >= 0) ? c.modelToView(lastOffs) : null; + + if ((lastOffs >= 0)) { + r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs); + } else { + r = null; + } } return offs; } @@ -694,10 +787,12 @@ public class Utilities { * @return the position {@code >= 0} if the request can be computed, otherwise * a value of -1 will be returned. * @exception BadLocationException if the offset is out of range + * + * @since 9 */ public static final int getPositionAbove(JTextComponent c, int offs, float x) throws BadLocationException { - return getPositionAbove(c, offs, (int) x); + return getPositionAbove(c, offs, x, true); } /** @@ -712,28 +807,45 @@ public class Utilities { * @return the position >= 0 if the request can be computed, otherwise * a value of -1 will be returned. * @exception BadLocationException if the offset is out of range + * + * @deprecated replaced by + * {@link #getPositionBelow(JTextComponent, int, float)} */ - public static final int getPositionBelow(JTextComponent c, int offs, int x) throws BadLocationException { + @Deprecated(since = "9") + public static final int getPositionBelow(JTextComponent c, int offs, int x) + throws BadLocationException + { + return getPositionBelow(c, offs, x, false); + } + + static final int getPositionBelow(JTextComponent c, int offs, float x, + boolean useFPAPI) throws BadLocationException + { int lastOffs = getRowEnd(c, offs) + 1; if (lastOffs <= 0) { return -1; } - int bestSpan = Integer.MAX_VALUE; + double bestSpan = Integer.MAX_VALUE; int n = c.getDocument().getLength(); - int y = 0; - Rectangle r = null; + double y = 0; + Rectangle2D r = null; if (lastOffs <= n) { - r = c.modelToView(lastOffs); - y = r.y; + r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs); + y = r.getY(); } - while ((r != null) && (y == r.y)) { - int span = Math.abs(x - r.x); + while ((r != null) && (y == r.getY())) { + double span = Math.abs(x - r.getX()); if (span < bestSpan) { offs = lastOffs; bestSpan = span; } lastOffs += 1; - r = (lastOffs <= n) ? c.modelToView(lastOffs) : null; + + if (lastOffs <= n) { + r = useFPAPI ? c.modelToView2D(lastOffs) : c.modelToView(lastOffs); + } else { + r = null; + } } return offs; } @@ -750,10 +862,12 @@ public class Utilities { * @return the position {@code >= 0} if the request can be computed, otherwise * a value of -1 will be returned. * @exception BadLocationException if the offset is out of range + * + * @since 9 */ public static final int getPositionBelow(JTextComponent c, int offs, float x) throws BadLocationException { - return getPositionBelow(c, offs, (int) x); + return getPositionBelow(c, offs, x, true); } /** @@ -1029,7 +1143,23 @@ public class Utilities { */ static int drawComposedText(View view, AttributeSet attr, Graphics g, int x, int y, int p0, int p1) - throws BadLocationException { + throws BadLocationException + { + return (int) drawComposedText(view, attr, g, x, y, p0, p1, false); + } + + static float drawComposedText(View view, AttributeSet attr, Graphics g, + float x, float y, int p0, int p1) + throws BadLocationException + { + return drawComposedText(view, attr, g, x, y, p0, p1, true); + } + + static float drawComposedText(View view, AttributeSet attr, Graphics g, + float x, float y, int p0, int p1, + boolean useFPAPI) + throws BadLocationException + { Graphics2D g2d = (Graphics2D)g; AttributedString as = (AttributedString)attr.getAttribute( StyleConstants.ComposedTextAttribute); @@ -1039,8 +1169,7 @@ public class Utilities { return x; AttributedCharacterIterator aci = as.getIterator(null, p0, p1); - return x + (int)SwingUtilities2.drawString( - getJComponent(view), g2d,aci,x,y); + return x + SwingUtilities2.drawString(getJComponent(view), g2d, aci, x, y); } /** diff --git a/jdk/src/java.desktop/share/classes/javax/swing/text/WrappedPlainView.java b/jdk/src/java.desktop/share/classes/javax/swing/text/WrappedPlainView.java index dbcc5fdd207..5549317d95c 100644 --- a/jdk/src/java.desktop/share/classes/javax/swing/text/WrappedPlainView.java +++ b/jdk/src/java.desktop/share/classes/javax/swing/text/WrappedPlainView.java @@ -25,8 +25,12 @@ package javax.swing.text; import java.awt.*; +import java.awt.font.FontRenderContext; import java.lang.ref.SoftReference; +import java.security.AccessController; +import java.security.PrivilegedAction; import javax.swing.event.*; +import static javax.swing.text.PlainView.isFPMethodOverriden; /** * View of plain text (text with only one font and color) @@ -86,17 +90,6 @@ public class WrappedPlainView extends BoxView implements TabExpander { return size; } - /** - * Returns the tab size set for the document, defaulting to 8. - * - * @implSpec This implementation calls {@link #getTabSize() getTabSize()}. - * - * @return the tab size - */ - protected float getFractionalTabSize() { - return getTabSize(); - } - /** * Renders a line of text, suppressing whitespace at the end * and expanding any tabs. This is implemented to make calls @@ -111,8 +104,17 @@ public class WrappedPlainView extends BoxView implements TabExpander { * @param y the starting Y position >= 0 * @see #drawUnselectedText * @see #drawSelectedText + * + * @deprecated replaced by + * {@link #drawLine(int, int, Graphics2D, float, float)} */ + @Deprecated(since = "9") protected void drawLine(int p0, int p1, Graphics g, int x, int y) { + drawLineImpl(p0, p1, g, x, y, false); + } + + private void drawLineImpl(int p0, int p1, Graphics g, float x, float y, + boolean useFPAPI) { Element lineMap = getElement(); Element line = lineMap.getElement(lineMap.getElementIndex(p0)); Element elem; @@ -143,10 +145,6 @@ public class WrappedPlainView extends BoxView implements TabExpander { * drawSelectedText so that the way selected and * unselected text are rendered can be customized. * - * @implSpec This implementation calls - * {@link #drawLine(int, int, Graphics, int, int) - * drawLine(p0, p1, (Graphics) g, (int) x, (int) y)}. - * * @param p0 the starting document location to use >= 0 * @param p1 the ending document location to use >= p1 * @param g the graphics context @@ -154,12 +152,17 @@ public class WrappedPlainView extends BoxView implements TabExpander { * @param y the starting Y position >= 0 * @see #drawUnselectedText * @see #drawSelectedText + * + * @since 9 */ protected void drawLine(int p0, int p1, Graphics2D g, float x, float y) { - drawLine(p0, p1, (Graphics) g, (int) x, (int) y); + drawLineImpl(p0, p1, g, x, y, true); } - private int drawText(Element elem, int p0, int p1, Graphics g, int x, int y) throws BadLocationException { + private float drawText(Element elem, int p0, int p1, Graphics g, + float x, float y) + throws BadLocationException + { p1 = Math.min(getDocument().getLength(), p1); AttributeSet attr = elem.getAttributes(); @@ -171,23 +174,23 @@ public class WrappedPlainView extends BoxView implements TabExpander { } else { if (sel0 == sel1 || selected == unselected) { // no selection, or it is invisible - x = drawUnselectedText(g, x, y, p0, p1); + x = callDrawUnselectedText(g, x, y, p0, p1); } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) { - x = drawSelectedText(g, x, y, p0, p1); + x = callDrawSelectedText(g, x, y, p0, p1); } else if (sel0 >= p0 && sel0 <= p1) { if (sel1 >= p0 && sel1 <= p1) { - x = drawUnselectedText(g, x, y, p0, sel0); - x = drawSelectedText(g, x, y, sel0, sel1); - x = drawUnselectedText(g, x, y, sel1, p1); + x = callDrawUnselectedText(g, x, y, p0, sel0); + x = callDrawSelectedText(g, x, y, sel0, sel1); + x = callDrawUnselectedText(g, x, y, sel1, p1); } else { - x = drawUnselectedText(g, x, y, p0, sel0); - x = drawSelectedText(g, x, y, sel0, p1); + x = callDrawUnselectedText(g, x, y, p0, sel0); + x = callDrawSelectedText(g, x, y, sel0, p1); } } else if (sel1 >= p0 && sel1 <= p1) { - x = drawSelectedText(g, x, y, p0, sel1); - x = drawUnselectedText(g, x, y, sel1, p1); + x = callDrawSelectedText(g, x, y, p0, sel1); + x = callDrawUnselectedText(g, x, y, sel1, p1); } else { - x = drawUnselectedText(g, x, y, p0, p1); + x = callDrawUnselectedText(g, x, y, p0, p1); } } @@ -205,14 +208,36 @@ public class WrappedPlainView extends BoxView implements TabExpander { * @param p1 the ending position in the model >= p0 * @return the X location of the end of the range >= 0 * @exception BadLocationException if the range is invalid + * + * @deprecated replaced by + * {@link #drawUnselectedText(Graphics2D, float, float, int, int)} */ + @Deprecated(since = "9") protected int drawUnselectedText(Graphics g, int x, int y, - int p0, int p1) throws BadLocationException { + int p0, int p1) throws BadLocationException + { + return (int) drawUnselectedTextImpl(g, x, y, p0, p1, false); + } + + private float callDrawUnselectedText(Graphics g, float x, float y, + int p0, int p1) + throws BadLocationException + { + return drawUnselectedTextOverridden && g instanceof Graphics2D + ? drawUnselectedText((Graphics2D) g, x, y, p0, p1) + : drawUnselectedText(g, (int) x, (int) y, p0, p1); + } + + private float drawUnselectedTextImpl(Graphics g, float x, float y, + int p0, int p1, boolean useFPAPI) + throws BadLocationException + { g.setColor(unselected); Document doc = getDocument(); Segment segment = SegmentCache.getSharedSegment(); doc.getText(p0, p1 - p0, segment); - int ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0); + float ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0, + null, useFPAPI); SegmentCache.releaseSharedSegment(segment); return ret; } @@ -221,10 +246,6 @@ public class WrappedPlainView extends BoxView implements TabExpander { * Renders the given range in the model as normal unselected * text. * - * @implSpec This implementation calls - * {@link #drawUnselectedText(Graphics, int, int, int, int) - * drawUnselectedText((Graphics)g, (int) x, (int) y, p0, p1)}. - * * @param g the graphics context * @param x the starting X coordinate >= 0 * @param y the starting Y coordinate >= 0 @@ -232,10 +253,12 @@ public class WrappedPlainView extends BoxView implements TabExpander { * @param p1 the ending position in the model >= p0 * @return the X location of the end of the range >= 0 * @exception BadLocationException if the range is invalid + * + * @since 9 */ protected float drawUnselectedText(Graphics2D g, float x, float y, int p0, int p1) throws BadLocationException { - return drawUnselectedText((Graphics) g, (int) x, (int) y, p0, p1); + return drawUnselectedTextImpl(g, x, y, p0, p1, true); } /** * Renders the given range in the model as selected text. This @@ -250,14 +273,37 @@ public class WrappedPlainView extends BoxView implements TabExpander { * @param p1 the ending position in the model >= p0 * @return the location of the end of the range. * @exception BadLocationException if the range is invalid + * + * @deprecated replaced by + * {@link #drawSelectedText(Graphics2D, float, float, int, int)} */ - protected int drawSelectedText(Graphics g, int x, - int y, int p0, int p1) throws BadLocationException { + @Deprecated(since = "9") + protected int drawSelectedText(Graphics g, int x, int y, int p0, int p1) + throws BadLocationException + { + return (int) drawSelectedTextImpl(g, x, y, p0, p1, false); + } + + private float callDrawSelectedText(Graphics g, float x, float y, + int p0, int p1) + throws BadLocationException + { + return drawSelectedTextOverridden && g instanceof Graphics2D + ? drawSelectedText((Graphics2D) g, x, y, p0, p1) + : drawSelectedText(g, (int) x, (int) y, p0, p1); + } + + private float drawSelectedTextImpl(Graphics g, float x, float y, + int p0, int p1, + boolean useFPAPI) + throws BadLocationException + { g.setColor(selected); Document doc = getDocument(); Segment segment = SegmentCache.getSharedSegment(); doc.getText(p0, p1 - p0, segment); - int ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0); + float ret = Utilities.drawTabbedText(this, segment, x, y, g, this, p0, + null, useFPAPI); SegmentCache.releaseSharedSegment(segment); return ret; } @@ -268,10 +314,6 @@ public class WrappedPlainView extends BoxView implements TabExpander { * the hosting component. It assumes the highlighter will render * the selected background. * - * @implSpec This implementation calls - * {@link #drawSelectedText(Graphics, int, int, int, int) - * drawSelectedText((Graphics)g, (int) x, (int) y, p0, p1)}. - * * @param g the graphics context * @param x the starting X coordinate >= 0 * @param y the starting Y coordinate >= 0 @@ -279,10 +321,12 @@ public class WrappedPlainView extends BoxView implements TabExpander { * @param p1 the ending position in the model >= p0 * @return the location of the end of the range. * @exception BadLocationException if the range is invalid + * + * @since 9 */ protected float drawSelectedText(Graphics2D g, float x, float y, int p0, int p1) throws BadLocationException { - return drawSelectedText((Graphics) g, (int) x, (int) y, p0, p1); + return drawSelectedTextImpl(g, x, y, p0, p1, true); } /** * Gives access to a buffer that can be used to fetch @@ -395,7 +439,13 @@ public class WrappedPlainView extends BoxView implements TabExpander { Component host = getContainer(); Font f = host.getFont(); metrics = host.getFontMetrics(f); - tabSize = getTabSize() * metrics.charWidth('m'); + if (useFloatingPointAPI) { + FontRenderContext frc = metrics.getFontRenderContext(); + float tabWidth = (float) f.getStringBounds("m", frc).getWidth(); + tabSize = getTabSize() * tabWidth; + } else { + tabSize = getTabSize() * metrics.charWidth('m'); + } } // --- TabExpander methods ------------------------------------------ @@ -413,7 +463,7 @@ public class WrappedPlainView extends BoxView implements TabExpander { public float nextTabStop(float x, int tabOffset) { if (tabSize == 0) return x; - int ntabs = ((int) x - tabBase) / tabSize; + float ntabs = (x - tabBase) / tabSize; return tabBase + ((ntabs + 1) * tabSize); } @@ -591,7 +641,7 @@ public class WrappedPlainView extends BoxView implements TabExpander { Segment lineBuffer; boolean widthChanging; int tabBase; - int tabSize; + float tabSize; boolean wordWrap; int sel0; @@ -668,6 +718,7 @@ public class WrappedPlainView extends BoxView implements TabExpander { int end = getEndOffset(); int p0 = start; int[] lineEnds = getLineEnds(); + boolean useDrawLineFP = drawLineOverridden && g instanceof Graphics2D; for (int i = 0; i < lineCount; i++) { int p1 = (lineEnds == null) ? end : start + lineEnds[i]; @@ -677,8 +728,11 @@ public class WrappedPlainView extends BoxView implements TabExpander { : p1; dh.paintLayeredHighlights(g, p0, hOffset, a, host, this); } - drawLine(p0, p1, g, x, y); - + if (useDrawLineFP) { + drawLine(p0, p1, (Graphics2D) g, (float) x, (float) y); + } else { + drawLine(p0, p1, g, x, y); + } p0 = p1; y += metrics.getHeight(); } @@ -929,4 +983,47 @@ public class WrappedPlainView extends BoxView implements TabExpander { int lineCount; SoftReference lineCache = null; } + + private final boolean drawLineOverridden; + private final boolean drawSelectedTextOverridden; + private final boolean drawUnselectedTextOverridden; + private final boolean useFloatingPointAPI; + + { + final Class CLS = getClass(); + final Class INT = Integer.TYPE; + final Class FP = Float.TYPE; + + drawLineOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {INT, INT, Graphics.class, INT, INT}; + Class[] fpTypes = {INT, INT, Graphics2D.class, FP, FP}; + return isFPMethodOverriden("drawLine", CLS, intTypes, fpTypes); + } + }); + + drawUnselectedTextOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {Graphics.class, INT, INT, INT, INT}; + Class[] fpTypes = {Graphics2D.class, FP, FP, INT, INT}; + return isFPMethodOverriden("drawUnselectedText", CLS, intTypes, fpTypes); + } + }); + + drawSelectedTextOverridden = AccessController + .doPrivileged(new PrivilegedAction() { + @Override + public Boolean run() { + Class[] intTypes = {Graphics.class, INT, INT, INT, INT}; + Class[] fpTypes = {Graphics2D.class, FP, FP, INT, INT}; + return isFPMethodOverriden("drawSelectedText", CLS, intTypes, fpTypes); + } + }); + + useFloatingPointAPI = drawUnselectedTextOverridden || drawSelectedTextOverridden; + } } diff --git a/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java b/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java index ab67aae0154..74085dd91c3 100644 --- a/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java +++ b/jdk/src/java.desktop/share/classes/sun/swing/SwingUtilities2.java @@ -30,6 +30,7 @@ import java.awt.*; import static java.awt.RenderingHints.*; import java.awt.event.*; import java.awt.font.*; +import java.awt.geom.Rectangle2D; import java.awt.geom.AffineTransform; import static java.awt.geom.AffineTransform.TYPE_FLIP; import static java.awt.geom.AffineTransform.TYPE_TRANSLATION; @@ -723,10 +724,31 @@ public class SwingUtilities2 { int length, int x, int y) { + return (int) drawChars(c, g, data, offset, length, x, y, false); + } + + public static float drawChars(JComponent c, Graphics g, + char[] data, + int offset, + int length, + float x, + float y) { + return drawChars(c, g, data, offset, length, x, y, true); + } + + public static float drawChars(JComponent c, Graphics g, + char[] data, + int offset, + int length, + float x, + float y, + boolean useFPAPI) { if ( length <= 0 ) { //no need to paint empty strings return x; } - int nextX = x + getFontMetrics(c, g).charsWidth(data, offset, length); + float nextX = x + getFontCharsWidth(data, offset, length, + getFontMetrics(c, g), + useFPAPI); if (isPrinting(g)) { Graphics2D g2d = getGraphics2D(g); if (g2d != null) { @@ -766,8 +788,14 @@ public class SwingUtilities2 { Object aaHint = (c == null) ? null : c.getClientProperty(KEY_TEXT_ANTIALIASING); - if (aaHint != null && (g instanceof Graphics2D)) { - Graphics2D g2 = (Graphics2D)g; + + if (!(g instanceof Graphics2D)) { + g.drawChars(data, offset, length, (int) x, (int) y); + return nextX; + } + + Graphics2D g2 = (Graphics2D) g; + if (aaHint != null) { Object oldContrast = null; Object oldAAValue = g2.getRenderingHint(KEY_TEXT_ANTIALIASING); @@ -788,7 +816,7 @@ public class SwingUtilities2 { } } - g.drawChars(data, offset, length, x, y); + g2.drawString(new String(data, offset, length), x, y); if (oldAAValue != null) { g2.setRenderingHint(KEY_TEXT_ANTIALIASING, oldAAValue); @@ -798,19 +826,59 @@ public class SwingUtilities2 { } } else { - g.drawChars(data, offset, length, x, y); + g2.drawString(new String(data, offset, length), x, y); } return nextX; } + public static float getFontCharWidth(char c, FontMetrics fm, + boolean useFPAPI) + { + return getFontCharsWidth(new char[]{c}, 0, 1, fm, useFPAPI); + } + + public static float getFontCharsWidth(char[] data, int offset, int len, + FontMetrics fm, + boolean useFPAPI) + { + return len == 0 ? 0 : getFontStringWidth(new String(data, offset, len), + fm, useFPAPI); + } + + public static float getFontStringWidth(String data, FontMetrics fm, + boolean useFPAPI) + { + if (useFPAPI) { + Rectangle2D bounds = fm.getFont() + .getStringBounds(data, fm.getFontRenderContext()); + return (float) bounds.getWidth(); + } else { + return fm.stringWidth(data); + } + } + /* * see documentation for drawChars * returns the advance */ public static float drawString(JComponent c, Graphics g, AttributedCharacterIterator iterator, - int x, - int y) { + int x, int y) + { + return drawStringImpl(c, g, iterator, x, y); + } + + public static float drawString(JComponent c, Graphics g, + AttributedCharacterIterator iterator, + float x, float y) + { + return drawStringImpl(c, g, iterator, x, y); + } + + private static float drawStringImpl(JComponent c, Graphics g, + AttributedCharacterIterator iterator, + float x, float y) + { float retVal; boolean isPrinting = isPrinting(g); @@ -825,8 +893,8 @@ public class SwingUtilities2 { Graphics2D g2d = getGraphics2D(g); if (g2d == null) { - g.drawString(iterator,x,y); //for the cases where advance - //matters it should not happen + g.drawString(iterator, (int)x, (int)y); //for the cases where advance + //matters it should not happen retVal = x; } else { diff --git a/jdk/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java b/jdk/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java index acd7323cd1b..34a3e2957e7 100644 --- a/jdk/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java +++ b/jdk/src/jdk.accessibility/windows/classes/com/sun/java/accessibility/internal/AccessBridge.java @@ -41,13 +41,13 @@ import javax.swing.plaf.TreeUI; import javax.accessibility.*; import com.sun.java.accessibility.util.*; +import java.awt.geom.Rectangle2D; import sun.awt.AWTAccessor; import sun.awt.AppContext; import sun.awt.SunToolkit; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.CountDownLatch; /* * Note: This class has to be public. It's loaded from the VM like this: @@ -1754,7 +1754,7 @@ final public class AccessBridge { if (child instanceof JTextComponent) { JTextComponent text = (JTextComponent) child; try { - r = text.modelToView(text.getCaretPosition()); + r = text.modelToView2D(text.getCaretPosition()).getBounds(); if (r != null) { Point p = text.getLocationOnScreen(); r.translate(p.x, p.y); diff --git a/jdk/test/javax/swing/text/Caret/8163124/CaretFloatingPointAPITest.java b/jdk/test/javax/swing/text/Caret/8163124/CaretFloatingPointAPITest.java new file mode 100644 index 00000000000..19d6d6a8607 --- /dev/null +++ b/jdk/test/javax/swing/text/Caret/8163124/CaretFloatingPointAPITest.java @@ -0,0 +1,447 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.awt.geom.Line2D; +import java.awt.geom.Point2D; +import java.awt.geom.Rectangle2D; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.event.ChangeListener; +import javax.swing.plaf.TextUI; +import javax.swing.text.BadLocationException; +import javax.swing.text.Caret; +import javax.swing.text.DefaultHighlighter; +import javax.swing.text.Document; +import javax.swing.text.Highlighter; +import javax.swing.text.JTextComponent; +import javax.swing.text.Position; + +/* + * @test + * @bug 8163175 + * @summary PlainView.modelToView() method should return Rectangle2D + * @run main/manual CaretFloatingPointAPITest + */ +public class CaretFloatingPointAPITest { + + private static volatile boolean testResult = false; + private static volatile CountDownLatch countDownLatch; + private static final String INSTRUCTIONS = "INSTRUCTIONS:\n\n" + + "Verify that cursor position is not rounded on HiDPI display.\n\n" + + "If the display does not support HiDPI mode press PASS.\n\n" + + "1. Press the Right-Arrow key several times to move the red caret" + + " in the text field.\n" + + "2. Check that the caret has the same position between chars" + + " in diffrent locations.\n\n" + + "If so, press PASS, else press FAIL.\n"; + + public static void main(String args[]) throws Exception { + countDownLatch = new CountDownLatch(1); + + SwingUtilities.invokeLater(CaretFloatingPointAPITest::createUI); + countDownLatch.await(15, TimeUnit.MINUTES); + + if (!testResult) { + throw new RuntimeException("Test fails!"); + } + } + + private static void createUI() { + + final JFrame mainFrame = new JFrame("Metal L&F icons test"); + GridBagLayout layout = new GridBagLayout(); + JPanel mainControlPanel = new JPanel(layout); + JPanel resultButtonPanel = new JPanel(layout); + + GridBagConstraints gbc = new GridBagConstraints(); + + JTextField textField = new JTextField("aaaaaaaaaaaaaaaaaaaaaaa"); + Dimension size = new Dimension(400, 100); + textField.setPreferredSize(size); + textField.setFont(textField.getFont().deriveFont(28.0f)); + textField.setCaretColor(Color.RED); + textField.setCaret(new CustomCaret()); + gbc.gridx = 0; + gbc.gridy = 0; + gbc.insets = new Insets(5, 15, 5, 15); + gbc.fill = GridBagConstraints.HORIZONTAL; + mainControlPanel.add(textField, gbc); + + JTextArea instructionTextArea = new JTextArea(); + instructionTextArea.setText(INSTRUCTIONS); + instructionTextArea.setEditable(false); + instructionTextArea.setBackground(Color.white); + + gbc.gridx = 0; + gbc.gridy = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + mainControlPanel.add(instructionTextArea, gbc); + + JButton passButton = new JButton("Pass"); + passButton.setActionCommand("Pass"); + passButton.addActionListener((ActionEvent e) -> { + testResult = true; + mainFrame.dispose(); + countDownLatch.countDown(); + + }); + + JButton failButton = new JButton("Fail"); + failButton.setActionCommand("Fail"); + failButton.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + mainFrame.dispose(); + countDownLatch.countDown(); + } + }); + + gbc.gridx = 0; + gbc.gridy = 0; + + resultButtonPanel.add(passButton, gbc); + + gbc.gridx = 1; + gbc.gridy = 0; + resultButtonPanel.add(failButton, gbc); + + gbc.gridx = 0; + gbc.gridy = 2; + mainControlPanel.add(resultButtonPanel, gbc); + + mainFrame.add(mainControlPanel); + mainFrame.pack(); + + mainFrame.addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + mainFrame.dispose(); + countDownLatch.countDown(); + } + }); + mainFrame.setVisible(true); + } + + static class CustomCaret implements Caret { + + private JTextComponent component; + private boolean visible; + private boolean selectionVisible = true; + int blinkRate; + int dot; + int mark; + Position.Bias dotBias; + Position.Bias markBias; + Object selectionTag; + Point2D magicCaretPosition; + + private MouseListener mouseListener = new CaretMouseListener(); + + @Override + public void install(JTextComponent c) { + this.component = c; + c.addMouseListener(mouseListener); + } + + @Override + public void deinstall(JTextComponent c) { + c.removeMouseListener(mouseListener); + this.component = null; + } + + @Override + public void paint(Graphics g) { + + if (component == null) { + return; + } + + int dot = getDot(); + Rectangle2D r = null; + try { + r = component.modelToView2D(dot); + } catch (BadLocationException e) { + return; + } + + if (r == null) { + return; + } + + Rectangle2D cr = getCaretRectangle(r); + repaint(cr.getBounds()); + + g.setColor(component.getCaretColor()); + float cx = (float) cr.getX(); + float cy = (float) cr.getY(); + float cw = (float) cr.getWidth(); + float ch = (float) cr.getHeight(); + float c = cx + cw / 2; + + Graphics2D g2d = (Graphics2D) g; + g2d.draw(new Line2D.Float(c, cy, c, cy + ch)); + g2d.draw(new Line2D.Float(cx, cy, cx + cw, cy)); + g2d.draw(new Line2D.Float(cx, cy + ch, cx + cw, cy + ch)); + } + + void repaint(Rectangle r) { + component.repaint(r); + } + + Rectangle2D getCaretRectangle(Rectangle2D r) { + int d = 3; + double cx = r.getX() - d; + double cy = r.getY(); + double cw = 2 * d; + double ch = r.getHeight(); + return new Rectangle2D.Double(cx, cy, cw, ch); + } + + @Override + public void addChangeListener(ChangeListener l) { + } + + @Override + public void removeChangeListener(ChangeListener l) { + } + + @Override + public boolean isVisible() { + return visible; + } + + @Override + public void setVisible(boolean v) { + this.visible = true; + } + + @Override + public boolean isSelectionVisible() { + return selectionVisible; + } + + @Override + public void setSelectionVisible(boolean v) { + this.selectionVisible = v; + updateSelection(); + } + + @Override + public void setMagicCaretPosition(Point p) { + magicCaretPosition = p; + } + + @Override + public Point getMagicCaretPosition() { + if (magicCaretPosition != null) { + return new Point((int) magicCaretPosition.getX(), + (int) magicCaretPosition.getY()); + } + return null; + } + + @Override + public void setBlinkRate(int rate) { + this.blinkRate = rate; + } + + @Override + public int getBlinkRate() { + return blinkRate; + } + + @Override + public int getDot() { + return dot; + } + + @Override + public int getMark() { + return mark; + } + + @Override + public void setDot(int dot) { + setDot(dot, Position.Bias.Forward); + } + + private void setDot(int dot, Position.Bias bias) { + handleSetDot(dot, bias); + updateSelection(); + } + + @Override + public void moveDot(int dot) { + moveDot(dot, Position.Bias.Forward); + } + + private void moveDot(int dot, Position.Bias bias) { + changeCaretPosition(dot, bias); + updateSelection(); + } + + void handleSetDot(int dot, Position.Bias dotBias) { + + if (component == null) { + return; + } + + Document doc = component.getDocument(); + if (doc != null) { + dot = Math.min(dot, doc.getLength()); + } + + dot = Math.max(dot, 0); + + if (dot == 0) { + dotBias = Position.Bias.Forward; + } + + mark = dot; + + if (this.dot != dot || this.dotBias != dotBias) { + changeCaretPosition(dot, dotBias); + updateSelection(); + } + + this.markBias = this.dotBias; + } + + void changeCaretPosition(int dot, Position.Bias dotBias) { + this.dot = dot; + this.dotBias = dotBias; + setMagicCaretPosition(null); + SwingUtilities.invokeLater(this::repaintNewCaret); + } + + private void updateSelection() { + Highlighter h = component.getHighlighter(); + if (h != null) { + int p0 = Math.min(dot, mark); + int p1 = Math.max(dot, mark); + + if (p0 == p1 || !selectionVisible) { + if (selectionTag != null) { + h.removeHighlight(selectionTag); + selectionTag = null; + } + } else { + try { + if (selectionTag != null) { + h.changeHighlight(selectionTag, p0, p1); + } else { + Highlighter.HighlightPainter p = getSelectionPainter(); + selectionTag = h.addHighlight(p0, p1, p); + } + } catch (BadLocationException e) { + throw new RuntimeException(e); + } + } + } + } + + void repaintNewCaret() { + if (component != null) { + TextUI mapper = component.getUI(); + Document doc = component.getDocument(); + if ((mapper != null) && (doc != null)) { + Rectangle2D newLoc; + try { + newLoc = mapper.modelToView2D(component, this.dot, this.dotBias); + } catch (BadLocationException e) { + newLoc = null; + } + if (newLoc != null) { + adjustVisibility(newLoc.getBounds()); + if (getMagicCaretPosition() == null) { + setMagicCaretPosition(new Point((int) newLoc.getX(), + (int) newLoc.getY())); + } + } + damage(newLoc.getBounds()); + } + } + } + + protected Highlighter.HighlightPainter getSelectionPainter() { + return DefaultHighlighter.DefaultPainter; + } + + protected void adjustVisibility(Rectangle nloc) { + if (component == null) { + return; + } + if (SwingUtilities.isEventDispatchThread()) { + component.scrollRectToVisible(nloc); + } else { + SwingUtilities.invokeLater(() -> { + component.scrollRectToVisible(nloc); + }); + } + } + + protected synchronized void damage(Rectangle r) { + if (r != null && component != null) { + component.repaint(r); + } + } + + private class CaretMouseListener extends MouseAdapter { + + @Override + public void mousePressed(MouseEvent e) { + Point pt = new Point(e.getX(), e.getY()); + Position.Bias[] biasRet = new Position.Bias[1]; + int pos = component.getUI().viewToModel(component, pt, biasRet); + if (biasRet[0] == null) { + biasRet[0] = Position.Bias.Forward; + } + if (pos >= 0) { + setDot(pos); + } + } + } + } +} diff --git a/jdk/test/javax/swing/text/JTextComponent/8156217/TextSelectionTest.java b/jdk/test/javax/swing/text/JTextComponent/8156217/TextSelectionTest.java new file mode 100644 index 00000000000..73fdf0d1076 --- /dev/null +++ b/jdk/test/javax/swing/text/JTextComponent/8156217/TextSelectionTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +import java.awt.BorderLayout; +import java.awt.FlowLayout; +import java.awt.Font; +import java.awt.event.WindowAdapter; +import java.awt.event.WindowEvent; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.SwingUtilities; +import javax.swing.text.JTextComponent; + +/** + * @test + * @bug 8156217 + * @summary Selected text is shifted on HiDPI display + * @run main/manual/othervm -Dsun.java2d.uiScale=2 TextSelectionTest + */ +public class TextSelectionTest { + + private static final String INSTRUCTIONS = "This is a manual test.\n" + + "\n" + + "Select the current text from the end to the beginning.\n" + + "\n" + + "If the text is slightly shiftted from one side to another\n" + + "and back during selection press Fail.\n" + + "Otherwise, press Pass."; + + private static final CountDownLatch latch = new CountDownLatch(1); + private static volatile boolean passed = false; + + public static void main(String[] args) throws Exception { + SwingUtilities.invokeAndWait(TextSelectionTest::createAndShowGUI); + latch.await(3, TimeUnit.MINUTES); + System.out.println("passed: " + passed); + if (!passed) { + throw new RuntimeException("Test fails!"); + } + } + + private static void createAndShowGUI() { + + JFrame frame = new JFrame("Follow the instructions below:"); + frame.setSize(700, 500); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + JPanel panel = new JPanel(new BorderLayout()); + JTextComponent textComponent = new JTextArea(INSTRUCTIONS); + textComponent.setEditable(false); + Font font = textComponent.getFont(); + font = font.deriveFont(24.0f); + textComponent.setFont(font); + panel.add(textComponent, BorderLayout.CENTER); + + JPanel buttonsPanel = new JPanel(new FlowLayout()); + JButton passButton = new JButton("Pass"); + passButton.addActionListener((e) -> { + passed = true; + latch.countDown(); + frame.dispose(); + }); + JButton failsButton = new JButton("Fail"); + failsButton.addActionListener((e) -> { + passed = false; + latch.countDown(); + frame.dispose(); + }); + + buttonsPanel.add(passButton); + buttonsPanel.add(failsButton); + panel.add(buttonsPanel, BorderLayout.SOUTH); + + frame.getContentPane().add(panel); + + frame.addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + latch.countDown(); + } + }); + frame.setVisible(true); + } +} diff --git a/jdk/test/javax/swing/text/View/8156217/FPMethodCalledTest.java b/jdk/test/javax/swing/text/View/8156217/FPMethodCalledTest.java new file mode 100644 index 00000000000..ed9693bbb0c --- /dev/null +++ b/jdk/test/javax/swing/text/View/8156217/FPMethodCalledTest.java @@ -0,0 +1,499 @@ +/* + * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.FlowLayout; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Robot; +import javax.swing.JFrame; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; +import javax.swing.SwingUtilities; +import javax.swing.UIManager; +import javax.swing.plaf.metal.MetalLookAndFeel; +import javax.swing.plaf.metal.MetalTextFieldUI; +import javax.swing.text.BadLocationException; +import javax.swing.text.Element; +import javax.swing.text.PasswordView; +import javax.swing.text.PlainView; +import javax.swing.text.View; +import javax.swing.text.WrappedPlainView; + +/** + * @test + * @bug 8156217 + * @key headful + * @summary Selected text is shifted on HiDPI display + * @run main FPMethodCalledTest + */ +public class FPMethodCalledTest { + + private static JFrame frame; + private static JTextField textField; + + public static void main(String[] args) throws Exception { + + for (Test test : TESTS) { + test(test); + } + } + + static void test(final Test test) throws Exception { + try { + Robot robot = new Robot(); + robot.setAutoDelay(50); + SwingUtilities.invokeAndWait(() -> { + createAndShowGUI(test); + }); + + robot.waitForIdle(); + + SwingUtilities.invokeAndWait(() -> { + textField.select(1, 3); + }); + + robot.waitForIdle(); + + SwingUtilities.invokeAndWait(() -> { + Resultable resultable = test.resultable; + if (!resultable.getResult()) { + throw new RuntimeException("Test fails for: " + resultable); + } + }); + } finally { + SwingUtilities.invokeAndWait(() -> { + if (frame != null) { + frame.dispose(); + } + }); + } + } + + static void createAndShowGUI(Test test) { + + try { + UIManager.setLookAndFeel(new MetalLookAndFeel()); + } catch (Exception e) { + throw new RuntimeException(e); + } + + frame = new JFrame(); + frame.setSize(300, 300); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + JPanel panel = new JPanel(new FlowLayout()); + + String text = "AAAAAAA"; + textField = test.isPasswordField() + ? new JPasswordField(text) + : new JTextField(text); + + textField.setUI(new MetalTextFieldUI() { + + @Override + public View create(Element elem) { + return test.createView(elem); + } + }); + + panel.add(textField); + frame.getContentPane().add(panel); + frame.setVisible(true); + } + + private static final Test[] TESTS = { + new Test() { + @Override + View createView(Element elem) { + PlainViewINTAPI view = new PlainViewINTAPI(elem); + resultable = view; + return view; + } + }, + new Test() { + @Override + View createView(Element elem) { + PlainViewFPAPI view = new PlainViewFPAPI(elem); + resultable = view; + return view; + } + }, + new Test() { + @Override + View createView(Element elem) { + PlainViewMixedAPI view = new PlainViewMixedAPI(elem); + resultable = view; + return view; + } + }, + new Test() { + @Override + View createView(Element elem) { + WrappedPlainViewINTAPI view = new WrappedPlainViewINTAPI(elem); + resultable = view; + return view; + } + }, + new Test() { + @Override + View createView(Element elem) { + WrappedPlainViewFPAPI view = new WrappedPlainViewFPAPI(elem); + resultable = view; + return view; + } + }, + new Test() { + @Override + View createView(Element elem) { + WrappedPlainViewMixedAPI view = new WrappedPlainViewMixedAPI(elem); + resultable = view; + return view; + } + }, + new Test(true) { + + @Override + View createView(Element elem) { + PasswordViewINTAPI view = new PasswordViewINTAPI(elem); + resultable = view; + return view; + } + }, + new Test(true) { + + @Override + View createView(Element elem) { + PasswordViewFPAPI view = new PasswordViewFPAPI(elem); + resultable = view; + return view; + } + }, + new Test(true) { + + @Override + View createView(Element elem) { + PasswordViewMixedAPI view = new PasswordViewMixedAPI(elem); + resultable = view; + return view; + } + } + }; + + static interface Resultable { + + boolean getResult(); + } + + static abstract class Test { + + Resultable resultable; + final boolean isPasswordField; + + public Test() { + this(false); + } + + public Test(boolean isPasswordField) { + this.isPasswordField = isPasswordField; + } + + boolean isPasswordField() { + return isPasswordField; + } + + abstract View createView(Element elem); + } + + static class PlainViewINTAPI extends PlainView implements Resultable { + + boolean drawLine = false; + boolean drawSelected = false; + boolean drawUnselected = false; + + public PlainViewINTAPI(Element elem) { + super(elem); + } + + @Override + protected void drawLine(int lineIndex, Graphics g, int x, int y) { + drawLine = true; + super.drawLine(lineIndex, g, x, y); + } + + @Override + protected int drawSelectedText(Graphics g, int x, int y, + int p0, int p1) throws BadLocationException { + drawSelected = true; + return super.drawSelectedText(g, x, y, p0, p1); + } + + @Override + protected int drawUnselectedText(Graphics g, int x, int y, + int p0, int p1) throws BadLocationException { + drawUnselected = true; + return super.drawUnselectedText(g, x, y, p0, p1); + } + + @Override + public boolean getResult() { + return drawLine && drawSelected && drawUnselected; + } + } + + static class PlainViewFPAPI extends PlainView implements Resultable { + + boolean drawLine = false; + boolean drawSelected = false; + boolean drawUnselected = false; + + public PlainViewFPAPI(Element elem) { + super(elem); + } + + @Override + protected void drawLine(int lineIndex, Graphics2D g, float x, float y) { + drawLine = true; + super.drawLine(lineIndex, g, x, y); + } + + @Override + protected float drawSelectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException { + drawSelected = true; + return super.drawSelectedText(g, x, y, p0, p1); + } + + @Override + protected float drawUnselectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException { + drawUnselected = true; + return super.drawUnselectedText(g, x, y, p0, p1); + } + + @Override + public boolean getResult() { + return drawSelected; + } + } + + static class PlainViewMixedAPI extends PlainView implements Resultable { + + boolean isIntMethodCalled = false; + boolean isFPMethodCalled = false; + + public PlainViewMixedAPI(Element elem) { + super(elem); + } + + @Override + protected int drawSelectedText(Graphics g, int x, int y, + int p0, int p1) throws BadLocationException { + isIntMethodCalled = true; + return super.drawSelectedText(g, x, y, p0, p1); + } + + @Override + protected float drawSelectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException { + isFPMethodCalled = true; + return super.drawSelectedText(g, x, y, p0, p1); + } + + @Override + public boolean getResult() { + return !isIntMethodCalled && isFPMethodCalled; + } + } + + static class WrappedPlainViewINTAPI extends WrappedPlainView implements Resultable { + + boolean drawLine = false; + boolean drawSelected = false; + boolean drawUnselected = false; + + public WrappedPlainViewINTAPI(Element elem) { + super(elem); + } + + @Override + protected void drawLine(int p0, int p1, Graphics g, int x, int y) { + drawLine = true; + super.drawLine(p0, p1, g, x, y); + } + + @Override + protected int drawSelectedText(Graphics g, int x, int y, + int p0, int p1) throws BadLocationException { + drawSelected = true; + return super.drawSelectedText(g, x, y, p0, p1); + } + + @Override + protected int drawUnselectedText(Graphics g, int x, int y, + int p0, int p1) throws BadLocationException { + drawUnselected = true; + return super.drawUnselectedText(g, x, y, p0, p1); + } + + @Override + public boolean getResult() { + return drawLine && drawSelected && drawUnselected; + } + } + + static class WrappedPlainViewFPAPI extends WrappedPlainView implements Resultable { + + boolean drawLine = false; + boolean drawSelected = false; + boolean drawUnselected = false; + + public WrappedPlainViewFPAPI(Element elem) { + super(elem); + } + + @Override + protected void drawLine(int p0, int p1, Graphics2D g, float x, float y) { + drawLine = true; + super.drawLine(p0, p1, g, x, y); + } + + @Override + protected float drawSelectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException { + drawSelected = true; + return super.drawSelectedText(g, x, y, p0, p1); + } + + @Override + protected float drawUnselectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException { + drawUnselected = true; + return super.drawUnselectedText(g, x, y, p0, p1); + } + + @Override + public boolean getResult() { + return drawLine && drawSelected && drawUnselected; + } + } + + static class WrappedPlainViewMixedAPI extends WrappedPlainView implements Resultable { + + boolean isIntMethodCalled = false; + boolean isFPMethodCalled = false; + + public WrappedPlainViewMixedAPI(Element elem) { + super(elem); + } + + @Override + protected int drawUnselectedText(Graphics g, int x, int y, + int p0, int p1) throws BadLocationException { + isIntMethodCalled = true; + return super.drawUnselectedText(g, x, y, p0, p1); + } + + @Override + protected float drawUnselectedText(Graphics2D g, float x, float y, + int p0, int p1) throws BadLocationException { + isFPMethodCalled = true; + return super.drawUnselectedText(g, x, y, p0, p1); + } + + @Override + public boolean getResult() { + return !isIntMethodCalled && isFPMethodCalled; + } + } + + static class PasswordViewINTAPI extends PasswordView implements Resultable { + + boolean isIntMethodCalled = false; + + public PasswordViewINTAPI(Element elem) { + super(elem); + + } + + @Override + protected int drawEchoCharacter(Graphics g, int x, int y, char c) { + isIntMethodCalled = true; + return super.drawEchoCharacter(g, x, y, c); + } + + @Override + public boolean getResult() { + return isIntMethodCalled; + } + } + + static class PasswordViewFPAPI extends PasswordView implements Resultable { + + boolean isFPMethodCalled = false; + + public PasswordViewFPAPI(Element elem) { + super(elem); + + } + + @Override + protected float drawEchoCharacter(Graphics2D g, float x, float y, char c) { + isFPMethodCalled = true; + return super.drawEchoCharacter(g, x, y, c); + } + + @Override + public boolean getResult() { + return isFPMethodCalled; + } + } + + static class PasswordViewMixedAPI extends PasswordView implements Resultable { + + boolean isIntMethodCalled = false; + boolean isFPMethodCalled = false; + + public PasswordViewMixedAPI(Element elem) { + super(elem); + + } + + @Override + protected int drawEchoCharacter(Graphics g, int x, int y, char c) { + isIntMethodCalled = true; + return super.drawEchoCharacter(g, x, y, c); + } + + @Override + protected float drawEchoCharacter(Graphics2D g, float x, float y, char c) { + isFPMethodCalled = true; + return super.drawEchoCharacter(g, x, y, c); + } + + @Override + public boolean getResult() { + return !isIntMethodCalled && isFPMethodCalled; + } + } +}