From 6288ce52487507801ad59dc4d97a804426cf1bfe Mon Sep 17 00:00:00 2001 From: Jurgen Date: Thu, 10 Nov 2022 20:56:59 +0200 Subject: [PATCH] Remove java 8 & 9 multi jar compatibility (#1148) * Integrated java9 code, removing multi-jar * Removed JavaFXCompatibility --- richtextfx/build.gradle | 29 +- .../fxmisc/richtext/MultiReleaseJarTest.java | 22 -- .../fxmisc/richtext/JavaFXCompatibility.java | 80 ---- .../org/fxmisc/richtext/ParagraphText.java | 8 +- .../java/org/fxmisc/richtext/TextExt.java | 5 +- .../java/org/fxmisc/richtext/TextFlowExt.java | 345 ++++-------------- .../org/fxmisc/richtext/TextFlowLayout.java | 292 +++++++-------- .../org/fxmisc/richtext/TextFlowSpan.java | 88 ++--- .../fxmisc/richtext/JavaFXCompatibility.java | 31 -- .../org/fxmisc/richtext/TextFlowExt.java | 175 --------- 10 files changed, 280 insertions(+), 795 deletions(-) delete mode 100644 richtextfx/src/integrationTest/java/org/fxmisc/richtext/MultiReleaseJarTest.java delete mode 100644 richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java rename richtextfx/src/main/{java9 => java}/org/fxmisc/richtext/TextFlowLayout.java (97%) rename richtextfx/src/main/{java9 => java}/org/fxmisc/richtext/TextFlowSpan.java (95%) delete mode 100644 richtextfx/src/main/java9/org/fxmisc/richtext/JavaFXCompatibility.java delete mode 100644 richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowExt.java diff --git a/richtextfx/build.gradle b/richtextfx/build.gradle index 561704891..0925f7b70 100644 --- a/richtextfx/build.gradle +++ b/richtextfx/build.gradle @@ -24,22 +24,12 @@ check.dependsOn integrationTest integrationTest.mustRunAfter test integrationTest.testClassesDirs = sourceSets.integrationTest.output.classesDirs -sourceSets { - java9 { - java { - srcDirs = ['src/main/java9'] - } - } -} - dependencies { api group: 'org.reactfx', name: 'reactfx', version: '2.0-M5' api group: 'org.fxmisc.undo', name: 'undofx', version: '2.1.1' api group: 'org.fxmisc.flowless', name: 'flowless', version: '0.6.9' api group: 'org.fxmisc.wellbehaved', name: 'wellbehavedfx', version: '0.3.3' - java9Implementation files(sourceSets.main.output.classesDirs) { builtBy compileJava } - testImplementation group: 'junit', name: 'junit', version: '4.12' integrationTestImplementation group: 'junit', name: 'junit', version: '4.12' @@ -52,30 +42,19 @@ dependencies { } compileJava { - if ( JavaVersion.current() == JavaVersion.VERSION_1_9 ) { - sourceCompatibility = 9 - targetCompatibility = 9 - } - options.deprecation = true -} - -compileJava9Java { - onlyIf { JavaVersion.current() == JavaVersion.VERSION_1_9 } sourceCompatibility = 9 targetCompatibility = 9 options.deprecation = true } jar { - into 'META-INF/versions/9', { from sourceSets.java9.output } manifest { attributes( 'Specification-Title': 'RichTextFX', 'Specification-Version': project.specificationVersion, 'Implementation-Title': 'RichTextFX', 'Implementation-Version': project.version, - 'Automatic-Module-Name': 'org.fxmisc.richtext', - 'Multi-Release': 'true') + 'Automatic-Module-Name': 'org.fxmisc.richtext') } } @@ -175,7 +154,6 @@ task javadocJar(type: Jar, dependsOn: javadoc) { } task sourcesJar(type: Jar) { - into 'java9', { from sourceSets.java9.allSource } from sourceSets.main.allSource classifier = 'sources' } @@ -249,10 +227,7 @@ if(doUploadArchives) { task fatJar(type: Jar, dependsOn: classes) { archiveAppendix = 'fat' - - manifest.attributes( 'Automatic-Module-Name': 'org.fxmisc.richtext', - 'Multi-Release': 'true' ) - into 'META-INF/versions/9', { from sourceSets.java9.output } + manifest.attributes( 'Automatic-Module-Name': 'org.fxmisc.richtext' ) from sourceSets.main.output from { configurations.compileClasspath.collect { it.isDirectory() ? it : zipTree(it) } } } diff --git a/richtextfx/src/integrationTest/java/org/fxmisc/richtext/MultiReleaseJarTest.java b/richtextfx/src/integrationTest/java/org/fxmisc/richtext/MultiReleaseJarTest.java deleted file mode 100644 index 7b9f2024e..000000000 --- a/richtextfx/src/integrationTest/java/org/fxmisc/richtext/MultiReleaseJarTest.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.fxmisc.richtext; - -import org.fxmisc.richtext.JavaFXCompatibility; -import org.junit.Test; - -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.assertFalse; - -public class MultiReleaseJarTest -{ - @Test - public void tests_correct_classes_are_used() { - - if ( System.getProperty( "javafx.version" ).split("\\.")[0].equals("8") ) { - assertTrue( JavaFXCompatibility.isJavaEight() ); - } - else { - assertFalse( JavaFXCompatibility.isJavaEight() ); - } - } - -} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java deleted file mode 100644 index 4d35288ef..000000000 --- a/richtextfx/src/main/java/org/fxmisc/richtext/JavaFXCompatibility.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.fxmisc.richtext; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.StringTokenizer; -import javafx.beans.property.ObjectProperty; -import javafx.css.StyleConverter; -import javafx.scene.paint.Paint; -import javafx.scene.text.Text; - -/** - * Uses reflection to make this project's code work on Java 8 and Java 9 in a single jar - */ -public class JavaFXCompatibility { - - private static boolean isJava9orLater; - - static { - try { - // Java 9 version-String Scheme: http://openjdk.java.net/jeps/223 - StringTokenizer st = new StringTokenizer(System.getProperty("java.version"), "._-+"); - int majorVersion = Integer.parseInt(st.nextToken()); - isJava9orLater = majorVersion >= 9; - } catch (Exception e) { - // Java 8 or older - } - } - - /** - * There is a Java 9 version of this that returns false in src/main/java9/... - * and is used to check if tests are running against a multi-release jar. - */ - public static boolean isJavaEight() { - return true; - } - - /** - * Java 8: javafx.scene.text.Text.impl_selectionFillProperty() - * Java 9+: javafx.scene.text.Text.selectionFillProperty() - */ - @SuppressWarnings("unchecked") - static ObjectProperty Text_selectionFillProperty(Text text) { - try { - if (mText_selectionFillProperty == null) { - mText_selectionFillProperty = Text.class.getMethod( - isJava9orLater ? "selectionFillProperty" : "impl_selectionFillProperty"); - } - return (ObjectProperty) mText_selectionFillProperty.invoke(text); - } catch(NoSuchMethodException | SecurityException | IllegalAccessException | - IllegalArgumentException | InvocationTargetException e) { - e.printStackTrace(); - throw new Error(e); - } - } - - private static Method mText_selectionFillProperty; - - /** - * Java 8: com.sun.javafx.css.converters.SizeConverter.SequenceConverter.getInstance() - * Java 9+: javafx.css.converter.SizeConverter.SequenceConverter.getInstance() - */ - @SuppressWarnings("unchecked") - static StyleConverter SizeConverter_SequenceConverter_getInstance() { - try { - if (mSizeConverter_SequenceConverter_getInstance == null) { - Class c = Class.forName(isJava9orLater - ? "javafx.css.converter.SizeConverter$SequenceConverter" - : "com.sun.javafx.css.converters.SizeConverter$SequenceConverter"); - mSizeConverter_SequenceConverter_getInstance = c.getMethod("getInstance"); - } - return (StyleConverter) mSizeConverter_SequenceConverter_getInstance.invoke(null); - } catch (ClassNotFoundException | NoSuchMethodException | SecurityException | - IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - e.printStackTrace(); - throw new Error(e); - } - } - - private static Method mSizeConverter_SequenceConverter_getInstance; -} diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java index b2461fe22..8dafd55fe 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/ParagraphText.java @@ -63,7 +63,7 @@ class ParagraphText extends TextFlowExt { private final SetChangeListener caretNodeListener; // FIXME: changing it currently has not effect, because - // Text.impl_selectionFillProperty().set(newFill) doesn't work + // Text.selectionFillProperty().set(newFill) doesn't work // properly for Text node inside a TextFlow (as of JDK8-b100). private final ObjectProperty highlightTextFill = new SimpleObjectProperty<>(Color.WHITE); public ObjectProperty highlightTextFillProperty() { @@ -145,7 +145,7 @@ public ObjectProperty highlightTextFillProperty() { // public void changed(ObservableValue observable, // Paint oldFill, Paint newFill) { // for(PumpedUpText text: textNodes()) -// text.impl_selectionFillProperty().set(newFill); +// text.selectionFillProperty().set(newFill); // } // }); @@ -155,7 +155,7 @@ public ObjectProperty highlightTextFillProperty() { TextExt t = (TextExt) n; // XXX: binding selectionFill to textFill, // see the note at highlightTextFill - JavaFXCompatibility.Text_selectionFillProperty(t).bind(t.fillProperty()); + t.selectionFillProperty().bind(t.fillProperty()); } getChildren().add(n); }); @@ -231,7 +231,7 @@ void dispose() { carets.removeListener( caretNodeListener ); getChildren().stream().filter( n -> n instanceof TextExt ).map( n -> (TextExt) n ) - .forEach( t -> JavaFXCompatibility.Text_selectionFillProperty(t).unbind() ); + .forEach( t -> t.selectionFillProperty().unbind() ); getChildren().clear(); } diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java index ac53ae5ea..611a192e9 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextExt.java @@ -5,6 +5,7 @@ import java.util.List; import javafx.beans.property.ObjectProperty; +import javafx.css.converter.SizeConverter; import javafx.css.CssMetaData; import javafx.css.StyleConverter; import javafx.css.Styleable; @@ -319,7 +320,7 @@ private static class StyleableProperties { ); private static final CssMetaData BORDER_DASH_ARRAY = new CustomCssMetaData<>( - "-rtfx-border-stroke-dash-array", JavaFXCompatibility.SizeConverter_SequenceConverter_getInstance(), + "-rtfx-border-stroke-dash-array", SizeConverter.SequenceConverter.getInstance(), new Double[0], n -> n.borderStrokeDashArray ); @@ -344,7 +345,7 @@ private static class StyleableProperties { ); private static final CssMetaData UNDERLINE_DASH_ARRAY = new CustomCssMetaData<>( - "-rtfx-underline-dash-array", JavaFXCompatibility.SizeConverter_SequenceConverter_getInstance(), + "-rtfx-underline-dash-array", SizeConverter.SequenceConverter.getInstance(), new Double[0], n -> n.underlineDashArray ); diff --git a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java index acb732db9..522d9c777 100644 --- a/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowExt.java @@ -1,87 +1,59 @@ package org.fxmisc.richtext; import static org.fxmisc.richtext.model.TwoDimensional.Bias.*; -import java.lang.reflect.Array; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; + import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; import java.util.List; +import javafx.geometry.Point2D; +import javafx.geometry.Rectangle2D; +import javafx.scene.control.IndexRange; import org.fxmisc.richtext.model.TwoLevelNavigator; -import javafx.scene.control.IndexRange; import javafx.scene.shape.PathElement; +import javafx.scene.text.HitInfo; import javafx.scene.text.TextFlow; -import javafx.scene.shape.LineTo; -import javafx.scene.shape.MoveTo; +import javafx.scene.shape.*; /** * Adds additional API to {@link TextFlow}. - * - * PLEASE NOTE that is has a Java 9+ version in src/main/java9 !!! - * */ class TextFlowExt extends TextFlow { - - private static Method mGetTextLayout; - static { - try { - mGetTextLayout = TextFlow.class.getDeclaredMethod("getTextLayout"); - } catch (NoSuchMethodException | SecurityException e) { - throw new RuntimeException(e); - } - mGetTextLayout.setAccessible(true); - } - - private static Object invoke(Method m, Object obj, Object... args) { - try { - return m.invoke(obj, args); - } catch (IllegalAccessException | IllegalArgumentException - | InvocationTargetException e) { - throw new RuntimeException(e); + + private TextFlowLayout layout; + + private TextFlowLayout textLayout() + { + if ( layout == null ) { + layout = new TextFlowLayout( this ); } + return layout; } - int getLineCount() { - return getLines().length; + return textLayout().getLineCount(); } int getLineStartPosition(int charIdx) { - TextLine[] lines = getLines(); - TwoLevelNavigator navigator = new TwoLevelNavigator( - () -> lines.length, - i -> lines[i].getLength()); + TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator(); int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor(); return navigator.position(currentLineIndex, 0).toOffset(); } int getLineEndPosition(int charIdx) { - TextLine[] lines = getLines(); - TwoLevelNavigator navigator = new TwoLevelNavigator( - () -> lines.length, - i -> lines[i].getLength()); - int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor(); - int minor = currentLineIndex == lines.length - 1 ? 0 : -1; - return navigator.position(currentLineIndex + 1, minor).toOffset(); + TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator(); + int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor() + 1; + int minor = (currentLineIndex == getLineCount()) ? 0 : -1; + return navigator.position(currentLineIndex, minor).toOffset(); } int getLineOfCharacter(int charIdx) { - TextLine[] lines = getLines(); - TwoLevelNavigator navigator = new TwoLevelNavigator( - () -> lines.length, - i -> lines[i].getLength()); + TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator(); return navigator.offsetToPosition(charIdx, Forward).getMajor(); } PathElement[] getCaretShape(int charIdx, boolean isLeading) { -// use if Java 9 becomes minimum requirement -// return caretShape(charIdx, isLeading); - return textLayout().getCaretShape(charIdx, isLeading, 0, 0); + return caretShape(charIdx, isLeading); } PathElement[] getRangeShape(IndexRange range) { @@ -89,9 +61,7 @@ PathElement[] getRangeShape(IndexRange range) { } PathElement[] getRangeShape(int from, int to) { -// use if Java 9 becomes minimum requirement -// return rangeShape(from, to); - return textLayout().getRange(from, to, TextLayout.TYPE_TEXT, 0, 0); + return rangeShape(from, to); } PathElement[] getUnderlineShape(IndexRange range) { @@ -105,66 +75,86 @@ PathElement[] getUnderlineShape(int from, int to) { /** * @param from The index of the first character. * @param to The index of the last character. - * @param offset Ignored (only implemented for Java 9+) - * @param waveRadius Ignored (only implemented for Java 9+) + * @param offset The distance below the baseline to draw the underline. + * @param waveRadius If non-zero, draw a wavy underline with arcs of this radius. * @return An array with the PathElement objects which define an * underline from the first to the last character. */ PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius) { // get a Path for the text underline - PathElement[] shape = textLayout().getRange(from, to, TextLayout.TYPE_UNDERLINE, 0, 0); - - // The shape is returned as a closed Path (a thin rectangle). - // If we use the Path as it is, this causes rendering issues. - // Hence we only use the MoveTo and the succeeding LineTo elements for the result - // so that simple line segments instead of rectangles are returned. List result = new ArrayList<>(); + + PathElement[] shape = rangeShape( from, to ); + // The shape is a closed Path for one or more rectangles AROUND the selected text. + // shape: [MoveTo origin, LineTo top R, LineTo bottom R, LineTo bottom L, LineTo origin, *] - boolean collect = false; - for (PathElement elem : shape) { - if (elem instanceof MoveTo) { // There seems to be no API to get the type of the PathElement - result.add(elem); - collect = true; - } else if (elem instanceof LineTo) { - if (collect) { - result.add(elem); - collect = false; + // Extract the bottom left and right coordinates for each rectangle to get the underline path. + for ( int ele = 2; ele < shape.length; ele += 5 ) + { + LineTo bl = (LineTo) shape[ele+1]; + LineTo br = (LineTo) shape[ele]; + + double y = snapSizeY( br.getY() + offset - 2.5 ); + double leftx = snapSizeX( bl.getX() ); + + if (waveRadius <= 0) { + result.add(new MoveTo( leftx, y )); + result.add(new LineTo( snapSizeX( br.getX() ), y )); + } + else { + // For larger wave radii increase the X radius to stretch out the wave. + double radiusX = waveRadius > 1 ? waveRadius * 1.25 : waveRadius; + double rightx = br.getX(); + result.add(new MoveTo( leftx, y )); + boolean sweep = true; + while ( leftx < rightx ) { + leftx += waveRadius * 2; + + if (leftx > rightx) { + // Since we are drawing the wave in segments, it is necessary to + // clip the final arc to avoid over/underflow with larger radii, + // so we must compute the y value for the point on the arc where + // x = rightx. + // To simplify the computation, we translate so that the center of + // the arc has x = 0, and the known endpoints have y = 0. + double dx = rightx - (leftx - waveRadius); + double dxsq = dx * dx; + double rxsq = radiusX * radiusX; + double rysq = waveRadius * waveRadius; + double dy = waveRadius * (Math.sqrt(1 - dxsq/rxsq) - Math.sqrt(1 - rysq/rxsq)); + + if (sweep) y -= dy; else y += dy; + leftx = rightx; + } + result.add(new ArcTo( radiusX, waveRadius, 0.0, leftx, y, false, sweep )); + sweep = !sweep; } } } - return result.toArray(new PathElement[0]); + return result.toArray(new PathElement[0]); } CharacterHit hitLine(double x, int lineIndex) { - return hit(x, getLineCenter(lineIndex)); + return hit(x, textLayout().getLineCenter( lineIndex )); } CharacterHit hit(double x, double y) { -// use if Java 9 becomes minimum requirement -// HitInfo hit = hitTest(new Point2D(x, y)); - HitInfo hit = textLayout().getHitInfo((float) x, (float) y); + TextFlowSpan span = textLayout().getLineSpan( (float) y ); + Rectangle2D lineBounds = span.getBounds(); + + HitInfo hit = hitTest(new Point2D(x, y)); int charIdx = hit.getCharIndex(); boolean leading = hit.isLeading(); - int lineIdx = getLineIndex((float) y); - if(lineIdx >= getLineCount()) { - return CharacterHit.insertionAt(getCharCount()); + if (y >= span.getBounds().getMaxY()) { + return CharacterHit.insertionAt(charIdx); } - TextLine[] lines = getLines(); - TextLine line = lines[lineIdx]; - RectBounds lineBounds = line.getBounds(); - - // If this is a wrapped paragraph and hit character is at end of hit line, - // make sure that the "character hit" stays at the end of the hit line - // (and not at the beginning of the next line). - if(lines.length > 1 && - lineIdx < lines.length - 1 && - charIdx + 1 >= line.getStart() + line.getLength() && - !leading) - { - leading = true; + if ( ! leading && getLineCount() > 1) { + // If this is a wrapped paragraph and hit character is at end of hit line, make sure that the + // "character hit" stays at the end of the hit line (and not at the beginning of the next line). + leading = (getLineOfCharacter(charIdx) + 1 < getLineCount() && charIdx + 1 >= span.getStart() + span.getLength()); } if(x < lineBounds.getMinX() || x > lineBounds.getMaxX()) { @@ -182,177 +172,4 @@ CharacterHit hit(double x, double y) { } } - private float getLineY(int index) { - TextLine[] lines = getLines(); - float spacing = (float) getLineSpacing(); - float lineY = 0; - for(int i = 0; i < index; ++i) { - lineY += lines[i].getBounds().getHeight() + spacing; - } - return lineY; - } - - private float getLineCenter(int index) { - return getLineY(index) + getLines()[index].getBounds().getHeight() / 2; - } - - private TextLine[] getLines() { - return textLayout().getLines(); - } - - private int getLineIndex(float y) { - return textLayout().getLineIndex(y); - } - - private int getCharCount() { - return textLayout().getCharCount(); - } - - private TextLayout textLayout() { - return GenericIceBreaker.proxy(TextLayout.class, invoke(mGetTextLayout, this)); - } - - /* ********************************************************************** * - * * - * GenericIceBreaker * - * * - * ********************************************************************** */ - - private static class GenericIceBreaker implements InvocationHandler { - private final Object delegate; - - private GenericIceBreaker(Object delegate) { - this.delegate = delegate; - } - - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Method delegateMethod = getDeclaredMethod(delegate.getClass(), method.getName(), method.getParameterTypes()); - - Object delegateMethodReturn = null; - try { - delegateMethodReturn = delegateMethod.invoke(delegate, args); - } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException("problems invoking " + method.getName()); - } - if (delegateMethodReturn == null) { - return null; - } - - if (method.getReturnType().isArray()) { - if (method.getReturnType().getComponentType().isInterface() - && !method.getReturnType().getComponentType().equals(delegateMethod.getReturnType().getComponentType())) { - - int arrayLength = Array.getLength(delegateMethodReturn); - Object retArray = Array.newInstance(method.getReturnType().getComponentType(), arrayLength); - for (int i = 0; i < arrayLength; i++) { - Array.set(retArray, - i, - proxy( - method.getReturnType().getComponentType(), - Array.get(delegateMethodReturn, i))); - } - - return retArray; - } - } - - if (method.getReturnType().isInterface() - && !method.getReturnType().equals(delegateMethod.getReturnType())) { - return proxy(method.getReturnType(), delegateMethodReturn); - } - - return delegateMethodReturn; - } - - @SuppressWarnings("unchecked") - static T proxy(Class iface, Object delegate) { - return (T) Proxy.newProxyInstance( - iface.getClassLoader(), - new Class[]{iface}, - new GenericIceBreaker(delegate)); - } - - private static final HashMap declaredMethodCache = new HashMap<>(); - - private static synchronized Method getDeclaredMethod(Class cls, String name, Class... paramTypes) - throws NoSuchMethodException, SecurityException - { - MethodCacheKey methodCacheKey = new MethodCacheKey(cls, name, paramTypes); - - Method m = declaredMethodCache.get(methodCacheKey); - if (m == null) { - m = cls.getDeclaredMethod(name, paramTypes); - m.setAccessible(true); - declaredMethodCache.put(methodCacheKey, m); - } - return m; - } - } - - private static class MethodCacheKey { - final Class cls; - final String name; - final Class[] paramTypes; - - MethodCacheKey(Class cls, String name, Class... paramTypes) { - this.cls = cls; - this.name = name; - this.paramTypes = paramTypes; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof MethodCacheKey)) - return false; - - MethodCacheKey key2 = (MethodCacheKey) obj; - return cls == key2.cls && name.equals(key2.name) && Arrays.equals(paramTypes, key2.paramTypes); - } - - @Override - public int hashCode() { - return cls.hashCode() + name.hashCode() + Arrays.hashCode(paramTypes); - } - } - - /* ********************************************************************** * - * * - * Proxy interfaces * - * * - * ********************************************************************** */ - - private interface TextLayout - { - static final int TYPE_TEXT = 1 << 0; - static final int TYPE_UNDERLINE = 1 << 1; - - TextLine[] getLines(); - int getLineIndex(float y); - int getCharCount(); - - HitInfo getHitInfo(float x, float y); - PathElement[] getCaretShape(int offset, boolean isLeading, float x, float y); - PathElement[] getRange(int start, int end, int type, float x, float y); - } - - private interface TextLine - { - int getLength(); - RectBounds getBounds(); - int getStart(); - } - - private interface RectBounds - { - float getMinX(); - float getMaxX(); - float getHeight(); - } - - private interface HitInfo - { - int getCharIndex(); - boolean isLeading(); - } } diff --git a/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowLayout.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowLayout.java similarity index 97% rename from richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowLayout.java rename to richtextfx/src/main/java/org/fxmisc/richtext/TextFlowLayout.java index 1918d5fe2..455d47920 100644 --- a/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowLayout.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowLayout.java @@ -1,146 +1,146 @@ -package org.fxmisc.richtext; - -import java.util.ArrayList; -import java.util.List; - -import org.fxmisc.richtext.model.TwoLevelNavigator; - -import javafx.beans.Observable; -import javafx.geometry.Bounds; -import javafx.geometry.Point2D; -import javafx.scene.Node; -import javafx.scene.shape.LineTo; -import javafx.scene.shape.MoveTo; -import javafx.scene.shape.PathElement; -import javafx.scene.text.Text; -import javafx.scene.text.TextFlow; - -/** - * @author Jurgen (admedfx@gmail.com) - */ -class TextFlowLayout -{ - private TextFlow flow; - private List lineMetrics = new ArrayList<>(); - private int lineCount = -1; - - private static final TextFlowSpan EMPTY_SPAN = new TextFlowSpan( 0, 0, 0, 0, 0 ); - - - TextFlowLayout( TextFlow tf ) { - tf.getChildren().addListener( (Observable ob) -> lineCount = -1 ); - tf.widthProperty().addListener( (Observable ob) -> lineCount = -1 ); - flow = tf; - } - - - float getLineCenter( int lineNo ) { - return getLineCount() > 0 ? lineMetrics.get( lineNo ).getCenterY() : 1.0f; - } - - - int getLineLength( int lineNo ) { - return getLineSpan( lineNo ).getLength(); - } - - - TextFlowSpan getLineSpan( int lineNo ) { - return getLineCount() > 0 ? lineMetrics.get( lineNo ) : EMPTY_SPAN; - } - - - TextFlowSpan getLineSpan( float y ) { - final int lastLine = getLineCount(); - if ( lastLine < 1 ) return EMPTY_SPAN; - return lineMetrics.stream().filter( tfs -> y < tfs.getBounds().getMaxY() ) - .findFirst().orElse( lineMetrics.get( Math.max(0,lastLine-1) ) ); - } - - - TwoLevelNavigator getTwoLevelNavigator() { - return new TwoLevelNavigator( this::getLineCount, this::getLineLength ); - } - - - /* - * Iterate through the nodes in the TextFlow to determine the number of lines of text. - * Also calculates the following metrics for each line along the way: line height, - * line width, centerY, length (character count), start (character offset from 1st line) - */ - int getLineCount() { - - if ( lineCount > -1 ) return lineCount; - - lineCount = 0; - lineMetrics.clear(); - double totLines = 0.0, prevMaxY = -1.0; - int totCharSoFar = 0; - - for ( Node n : flow.getChildrenUnmodifiable() ) if ( n.isManaged() ) { - - Bounds nodeBounds = n.getBoundsInParent(); - int length = (n instanceof Text) ? ((Text) n).getText().length() : 1; - PathElement[] shape = flow.rangeShape( totCharSoFar, totCharSoFar+length ); - double lines = Math.max( 1.0, Math.floor( shape.length / 5 ) ); - double nodeMinY = Math.max( 0.0, nodeBounds.getMinY() ); - - if ( lines > 1.0 ) { // Staggered multiline text node - if ( totLines > 0.0 ) lines--; - totLines += lines; - } - else if ( nodeMinY >= prevMaxY ) { // Node is on next line - totLines++; - } - - if ( lineMetrics.size() < totLines ) { // Add additional lines - - if ( shape.length == 0 ) { - lineMetrics.add( new TextFlowSpan( totCharSoFar, length, nodeMinY, nodeBounds.getWidth(), nodeBounds.getHeight() ) ); - totCharSoFar += length; - } - else for ( int ele = 1; ele < shape.length; ele += 5 ) { - // Calculate the segment's line's length and width up to this point - LineTo eleLine = (LineTo) shape[ele]; - double segWidth = eleLine.getX(), lineMinY = eleLine.getY(); - double charHeight = ((LineTo) shape[ele+1]).getY() - lineMinY; - Point2D endPoint = new Point2D( segWidth-1, lineMinY + charHeight / 2 ); - - // hitTest queries TextFlow layout internally and returns the position of the - // last char (nearest endPoint) on the line, irrespective of the current Text node ! - int segLen = flow.hitTest( endPoint ).getCharIndex(); - segLen -= totCharSoFar - 1; - - if ( ele == 1 && nodeMinY < prevMaxY ) { - adjustLineMetrics( segLen, segWidth - ((MoveTo) shape[ele-1]).getX(), charHeight ); - } - else { - lineMetrics.add( new TextFlowSpan( totCharSoFar, segLen, lineMinY, segWidth, charHeight ) ); - } - - totCharSoFar += segLen; - } - } - else { - // Adjust current line metrics with additional Text or Node embedded in this line - adjustLineMetrics( length, nodeBounds.getWidth(), nodeBounds.getHeight() ); - totCharSoFar += length; - } - - prevMaxY = Math.max( prevMaxY, nodeBounds.getMaxY() ); - } - - lineCount = (int) totLines; - if ( lineCount > 0 ) return lineCount; - lineCount = -1; - return 0; - } - - - private void adjustLineMetrics( int length, double width, double height ) { - TextFlowSpan span = lineMetrics.get( lineMetrics.size()-1 ); - span.addLengthAndWidth( length, width ); - if ( height > span.getHeight() ) { - span.setHeight( height ); - } - } -} +package org.fxmisc.richtext; + +import java.util.ArrayList; +import java.util.List; + +import org.fxmisc.richtext.model.TwoLevelNavigator; + +import javafx.beans.Observable; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.shape.LineTo; +import javafx.scene.shape.MoveTo; +import javafx.scene.shape.PathElement; +import javafx.scene.text.Text; +import javafx.scene.text.TextFlow; + +/** + * @author Jurgen (admedfx@gmail.com) + */ +class TextFlowLayout +{ + private TextFlow flow; + private List lineMetrics = new ArrayList<>(); + private int lineCount = -1; + + private static final TextFlowSpan EMPTY_SPAN = new TextFlowSpan( 0, 0, 0, 0, 0 ); + + + TextFlowLayout( TextFlow tf ) { + tf.getChildren().addListener( (Observable ob) -> lineCount = -1 ); + tf.widthProperty().addListener( (Observable ob) -> lineCount = -1 ); + flow = tf; + } + + + float getLineCenter( int lineNo ) { + return getLineCount() > 0 ? lineMetrics.get( lineNo ).getCenterY() : 1.0f; + } + + + int getLineLength( int lineNo ) { + return getLineSpan( lineNo ).getLength(); + } + + + TextFlowSpan getLineSpan( int lineNo ) { + return getLineCount() > 0 ? lineMetrics.get( lineNo ) : EMPTY_SPAN; + } + + + TextFlowSpan getLineSpan( float y ) { + final int lastLine = getLineCount(); + if ( lastLine < 1 ) return EMPTY_SPAN; + return lineMetrics.stream().filter( tfs -> y < tfs.getBounds().getMaxY() ) + .findFirst().orElse( lineMetrics.get( Math.max(0,lastLine-1) ) ); + } + + + TwoLevelNavigator getTwoLevelNavigator() { + return new TwoLevelNavigator( this::getLineCount, this::getLineLength ); + } + + + /* + * Iterate through the nodes in the TextFlow to determine the number of lines of text. + * Also calculates the following metrics for each line along the way: line height, + * line width, centerY, length (character count), start (character offset from 1st line) + */ + int getLineCount() { + + if ( lineCount > -1 ) return lineCount; + + lineCount = 0; + lineMetrics.clear(); + double totLines = 0.0, prevMaxY = -1.0; + int totCharSoFar = 0; + + for ( Node n : flow.getChildrenUnmodifiable() ) if ( n.isManaged() ) { + + Bounds nodeBounds = n.getBoundsInParent(); + int length = (n instanceof Text) ? ((Text) n).getText().length() : 1; + PathElement[] shape = flow.rangeShape( totCharSoFar, totCharSoFar+length ); + double lines = Math.max( 1.0, Math.floor( shape.length / 5 ) ); + double nodeMinY = Math.max( 0.0, nodeBounds.getMinY() ); + + if ( lines > 1.0 ) { // Staggered multiline text node + if ( totLines > 0.0 ) lines--; + totLines += lines; + } + else if ( nodeMinY >= prevMaxY ) { // Node is on next line + totLines++; + } + + if ( lineMetrics.size() < totLines ) { // Add additional lines + + if ( shape.length == 0 ) { + lineMetrics.add( new TextFlowSpan( totCharSoFar, length, nodeMinY, nodeBounds.getWidth(), nodeBounds.getHeight() ) ); + totCharSoFar += length; + } + else for ( int ele = 1; ele < shape.length; ele += 5 ) { + // Calculate the segment's line's length and width up to this point + LineTo eleLine = (LineTo) shape[ele]; + double segWidth = eleLine.getX(), lineMinY = eleLine.getY(); + double charHeight = ((LineTo) shape[ele+1]).getY() - lineMinY; + Point2D endPoint = new Point2D( segWidth-1, lineMinY + charHeight / 2 ); + + // hitTest queries TextFlow layout internally and returns the position of the + // last char (nearest endPoint) on the line, irrespective of the current Text node ! + int segLen = flow.hitTest( endPoint ).getCharIndex(); + segLen -= totCharSoFar - 1; + + if ( ele == 1 && nodeMinY < prevMaxY ) { + adjustLineMetrics( segLen, segWidth - ((MoveTo) shape[ele-1]).getX(), charHeight ); + } + else { + lineMetrics.add( new TextFlowSpan( totCharSoFar, segLen, lineMinY, segWidth, charHeight ) ); + } + + totCharSoFar += segLen; + } + } + else { + // Adjust current line metrics with additional Text or Node embedded in this line + adjustLineMetrics( length, nodeBounds.getWidth(), nodeBounds.getHeight() ); + totCharSoFar += length; + } + + prevMaxY = Math.max( prevMaxY, nodeBounds.getMaxY() ); + } + + lineCount = (int) totLines; + if ( lineCount > 0 ) return lineCount; + lineCount = -1; + return 0; + } + + + private void adjustLineMetrics( int length, double width, double height ) { + TextFlowSpan span = lineMetrics.get( lineMetrics.size()-1 ); + span.addLengthAndWidth( length, width ); + if ( height > span.getHeight() ) { + span.setHeight( height ); + } + } +} diff --git a/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowSpan.java b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowSpan.java similarity index 95% rename from richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowSpan.java rename to richtextfx/src/main/java/org/fxmisc/richtext/TextFlowSpan.java index 35725e1ac..b81ee0197 100644 --- a/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowSpan.java +++ b/richtextfx/src/main/java/org/fxmisc/richtext/TextFlowSpan.java @@ -1,44 +1,44 @@ -package org.fxmisc.richtext; - -import javafx.geometry.Rectangle2D; - -/** - * @author Jurgen (admedfx@gmail.com) - */ -class TextFlowSpan -{ - private Rectangle2D bounds; - private double y, width, height; - private int start, length; - - TextFlowSpan( int start, int length, double minY, double width, double height ) { - this.start = start; - this.length = length; - this.height = height; - this.width = width; - y = minY; - } - - Rectangle2D getBounds() { - if ( bounds == null ) { - bounds = new Rectangle2D( 0, y, width, height ); - } - return bounds; - } - - float getCenterY() { - return (float) (y + height / 2); - } - - int getStart() { return start; } - int getLength() { return length; } - double getHeight() { return height; } - double getWidth() { return width; } - - void setHeight( double h ) { height = h; bounds = null; } - - void addLengthAndWidth( int len, double w ) { - width += w + 1; bounds = null; - length += len; - } -} +package org.fxmisc.richtext; + +import javafx.geometry.Rectangle2D; + +/** + * @author Jurgen (admedfx@gmail.com) + */ +class TextFlowSpan +{ + private Rectangle2D bounds; + private double y, width, height; + private int start, length; + + TextFlowSpan( int start, int length, double minY, double width, double height ) { + this.start = start; + this.length = length; + this.height = height; + this.width = width; + y = minY; + } + + Rectangle2D getBounds() { + if ( bounds == null ) { + bounds = new Rectangle2D( 0, y, width, height ); + } + return bounds; + } + + float getCenterY() { + return (float) (y + height / 2); + } + + int getStart() { return start; } + int getLength() { return length; } + double getHeight() { return height; } + double getWidth() { return width; } + + void setHeight( double h ) { height = h; bounds = null; } + + void addLengthAndWidth( int len, double w ) { + width += w + 1; bounds = null; + length += len; + } +} diff --git a/richtextfx/src/main/java9/org/fxmisc/richtext/JavaFXCompatibility.java b/richtextfx/src/main/java9/org/fxmisc/richtext/JavaFXCompatibility.java deleted file mode 100644 index 403829c4b..000000000 --- a/richtextfx/src/main/java9/org/fxmisc/richtext/JavaFXCompatibility.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.fxmisc.richtext; - -import javafx.beans.property.ObjectProperty; -import javafx.css.converter.SizeConverter; -import javafx.css.StyleConverter; -import javafx.scene.paint.Paint; -import javafx.scene.text.Text; - -/* ************************************************* * - * * - * Also look for and remove deprecated methods !!! * - * * - * ************************************************* */ -/** - * Used to use reflection to make this project's code work on Java 8 and Java 9 in a single jar - */ -@Deprecated -public class JavaFXCompatibility { - - static public boolean isJavaEight() { - return false; - } - - static ObjectProperty Text_selectionFillProperty(Text text) { - return text.selectionFillProperty(); - } - - static StyleConverter SizeConverter_SequenceConverter_getInstance() { - return SizeConverter.SequenceConverter.getInstance(); - } -} diff --git a/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowExt.java b/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowExt.java deleted file mode 100644 index 0beaca2a1..000000000 --- a/richtextfx/src/main/java9/org/fxmisc/richtext/TextFlowExt.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.fxmisc.richtext; - -import static org.fxmisc.richtext.model.TwoDimensional.Bias.*; - -import java.util.ArrayList; -import java.util.List; - -import javafx.geometry.Point2D; -import javafx.geometry.Rectangle2D; -import javafx.scene.control.IndexRange; -import org.fxmisc.richtext.model.TwoLevelNavigator; - -import javafx.scene.shape.PathElement; -import javafx.scene.text.HitInfo; -import javafx.scene.text.TextFlow; -import javafx.scene.shape.*; - -/** - * Adds additional API to {@link TextFlow}. - */ -class TextFlowExt extends TextFlow { - - private TextFlowLayout layout; - - private TextFlowLayout textLayout() - { - if ( layout == null ) { - layout = new TextFlowLayout( this ); - } - return layout; - } - - int getLineCount() { - return textLayout().getLineCount(); - } - - int getLineStartPosition(int charIdx) { - TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator(); - int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor(); - return navigator.position(currentLineIndex, 0).toOffset(); - } - - int getLineEndPosition(int charIdx) { - TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator(); - int currentLineIndex = navigator.offsetToPosition(charIdx, Forward).getMajor() + 1; - int minor = (currentLineIndex == getLineCount()) ? 0 : -1; - return navigator.position(currentLineIndex, minor).toOffset(); - } - - int getLineOfCharacter(int charIdx) { - TwoLevelNavigator navigator = textLayout().getTwoLevelNavigator(); - return navigator.offsetToPosition(charIdx, Forward).getMajor(); - } - - PathElement[] getCaretShape(int charIdx, boolean isLeading) { - return caretShape(charIdx, isLeading); - } - - PathElement[] getRangeShape(IndexRange range) { - return getRangeShape(range.getStart(), range.getEnd()); - } - - PathElement[] getRangeShape(int from, int to) { - return rangeShape(from, to); - } - - PathElement[] getUnderlineShape(IndexRange range) { - return getUnderlineShape(range.getStart(), range.getEnd()); - } - - PathElement[] getUnderlineShape(int from, int to) { - return getUnderlineShape(from, to, 0, 0); - } - - /** - * @param from The index of the first character. - * @param to The index of the last character. - * @param offset The distance below the baseline to draw the underline. - * @param waveRadius If non-zero, draw a wavy underline with arcs of this radius. - * @return An array with the PathElement objects which define an - * underline from the first to the last character. - */ - PathElement[] getUnderlineShape(int from, int to, double offset, double waveRadius) { - // get a Path for the text underline - List result = new ArrayList<>(); - - PathElement[] shape = rangeShape( from, to ); - // The shape is a closed Path for one or more rectangles AROUND the selected text. - // shape: [MoveTo origin, LineTo top R, LineTo bottom R, LineTo bottom L, LineTo origin, *] - - // Extract the bottom left and right coordinates for each rectangle to get the underline path. - for ( int ele = 2; ele < shape.length; ele += 5 ) - { - LineTo bl = (LineTo) shape[ele+1]; - LineTo br = (LineTo) shape[ele]; - - double y = snapSizeY( br.getY() + offset - 2.5 ); - double leftx = snapSizeX( bl.getX() ); - - if (waveRadius <= 0) { - result.add(new MoveTo( leftx, y )); - result.add(new LineTo( snapSizeX( br.getX() ), y )); - } - else { - // For larger wave radii increase the X radius to stretch out the wave. - double radiusX = waveRadius > 1 ? waveRadius * 1.25 : waveRadius; - double rightx = br.getX(); - result.add(new MoveTo( leftx, y )); - boolean sweep = true; - while ( leftx < rightx ) { - leftx += waveRadius * 2; - - if (leftx > rightx) { - // Since we are drawing the wave in segments, it is necessary to - // clip the final arc to avoid over/underflow with larger radii, - // so we must compute the y value for the point on the arc where - // x = rightx. - // To simplify the computation, we translate so that the center of - // the arc has x = 0, and the known endpoints have y = 0. - double dx = rightx - (leftx - waveRadius); - double dxsq = dx * dx; - double rxsq = radiusX * radiusX; - double rysq = waveRadius * waveRadius; - double dy = waveRadius * (Math.sqrt(1 - dxsq/rxsq) - Math.sqrt(1 - rysq/rxsq)); - - if (sweep) y -= dy; else y += dy; - leftx = rightx; - } - result.add(new ArcTo( radiusX, waveRadius, 0.0, leftx, y, false, sweep )); - sweep = !sweep; - } - } - } - - return result.toArray(new PathElement[0]); - } - - CharacterHit hitLine(double x, int lineIndex) { - return hit(x, textLayout().getLineCenter( lineIndex )); - } - - CharacterHit hit(double x, double y) { - TextFlowSpan span = textLayout().getLineSpan( (float) y ); - Rectangle2D lineBounds = span.getBounds(); - - HitInfo hit = hitTest(new Point2D(x, y)); - int charIdx = hit.getCharIndex(); - boolean leading = hit.isLeading(); - - if (y >= span.getBounds().getMaxY()) { - return CharacterHit.insertionAt(charIdx); - } - - if ( ! leading && getLineCount() > 1) { - // If this is a wrapped paragraph and hit character is at end of hit line, make sure that the - // "character hit" stays at the end of the hit line (and not at the beginning of the next line). - leading = (getLineOfCharacter(charIdx) + 1 < getLineCount() && charIdx + 1 >= span.getStart() + span.getLength()); - } - - if(x < lineBounds.getMinX() || x > lineBounds.getMaxX()) { - if(leading) { - return CharacterHit.insertionAt(charIdx); - } else { - return CharacterHit.insertionAt(charIdx + 1); - } - } else { - if(leading) { - return CharacterHit.leadingHalfOf(charIdx); - } else { - return CharacterHit.trailingHalfOf(charIdx); - } - } - } - -}