diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 000000000..aca31eb18 --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,48 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven + +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. + +name: Java CI with Maven + +on: + workflow_dispatch: + push: + branches: [ "develop" ] + pull_request: + branches: [ "develop" ] + +jobs: + build: + name: Java CI with Maven + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + statuses: write + + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + steps: + - uses: actions/checkout@v4 + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: maven + server-id: github + server-username: GITHUB_ACTOR + server-password: GITHUB_TOKEN + - name: Build with Maven + run: mvn clean install + + - name: Publish to GitHub Packages + run: mvn deploy + + diff --git a/jhotdraw-core/jgiven-reports/org.jhotdraw.draw.figure.BDDEllipse.EllipseFigureBDDTest.json b/jhotdraw-core/jgiven-reports/org.jhotdraw.draw.figure.BDDEllipse.EllipseFigureBDDTest.json new file mode 100644 index 000000000..ed2e1a4a5 --- /dev/null +++ b/jhotdraw-core/jgiven-reports/org.jhotdraw.draw.figure.BDDEllipse.EllipseFigureBDDTest.json @@ -0,0 +1,202 @@ +{ + "className": "org.jhotdraw.draw.figure.BDDEllipse.EllipseFigureBDDTest", + "name": "Ellipse Figure BDD", + "scenarios": [ + { + "className": "org.jhotdraw.draw.figure.BDDEllipse.EllipseFigureBDDTest", + "testMethodName": "ellipse_resizes_when_bounds_are_set", + "description": "ellipse resizes when bounds are set", + "tagIds": [], + "explicitParameters": [], + "derivedParameters": [], + "scenarioCases": [ + { + "caseNr": 1, + "steps": [ + { + "name": "an ellipse", + "words": [ + { + "value": "Given", + "isIntroWord": true + }, + { + "value": "an ellipse" + } + ], + "status": "PASSED", + "durationInNanos": 21426208, + "depth": 0, + "parentFailed": false + }, + { + "name": "bounds are set", + "words": [ + { + "value": "When", + "isIntroWord": true + }, + { + "value": "bounds are set" + }, + { + "value": "0.0", + "argumentInfo": { + "argumentName": "x1", + "formattedValue": "0.0" + } + }, + { + "value": "0.0", + "argumentInfo": { + "argumentName": "y1", + "formattedValue": "0.0" + } + }, + { + "value": "100.0", + "argumentInfo": { + "argumentName": "x2", + "formattedValue": "100.0" + } + }, + { + "value": "50.0", + "argumentInfo": { + "argumentName": "y2", + "formattedValue": "50.0" + } + } + ], + "status": "FAILED", + "durationInNanos": 18851584, + "depth": 0, + "parentFailed": false + }, + { + "name": "ellipse should have size", + "words": [ + { + "value": "Then", + "isIntroWord": true + }, + { + "value": "ellipse should have size" + }, + { + "value": "100.0", + "argumentInfo": { + "argumentName": "width", + "formattedValue": "100.0" + } + }, + { + "value": "50.0", + "argumentInfo": { + "argumentName": "height", + "formattedValue": "50.0" + } + } + ], + "status": "SKIPPED", + "durationInNanos": 0, + "depth": 0, + "parentFailed": false + } + ], + "explicitArguments": [], + "derivedArguments": [], + "status": "FAILED", + "errorMessage": "java.lang.NullPointerException: Cannot invoke \"org.jhotdraw.draw.figure.EllipseFigure.setBounds(java.awt.geom.Point2D$Double, java.awt.geom.Point2D$Double)\" because \"this.ellipse\" is null", + "stackTrace": [ + "org.jhotdraw.draw.figure.BDDEllipse.WhenSettingBounds.bounds_are_set(WhenSettingBounds.java:16)", + "org.jhotdraw.draw.figure.BDDEllipse.WhenSettingBounds$ByteBuddy$rriOpszZ.bounds_are_set$accessor$FNcLk1s9(Unknown Source)", + "org.jhotdraw.draw.figure.BDDEllipse.WhenSettingBounds$ByteBuddy$rriOpszZ$auxiliary$zGYJXUzv.call(Unknown Source)", + "org.jhotdraw.draw.figure.BDDEllipse.WhenSettingBounds$ByteBuddy$rriOpszZ.bounds_are_set(Unknown Source)", + "org.jhotdraw.draw.figure.BDDEllipse.EllipseFigureBDDTest.ellipse_resizes_when_bounds_are_set(EllipseFigureBDDTest.java:13)", + "java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)", + "org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:701)", + "org.junit.platform.commons.support.ReflectionSupport.invokeMethod(ReflectionSupport.java:502)", + "org.junit.jupiter.engine.support.MethodReflectionUtils.invoke(MethodReflectionUtils.java:45)", + "org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:61)", + "org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:124)", + "org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:163)", + "org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:148)", + "org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)", + "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:123)", + "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:105)", + "org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:99)", + "org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:66)", + "org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:47)", + "org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:39)", + "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:104)", + "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:98)", + "org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invokeVoid(InterceptingExecutableInvoker.java:71)", + "org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$0(TestMethodTestDescriptor.java:219)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:215)", + "org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:157)", + "org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:70)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:176)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)", + "org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)", + "java.base/java.util.ArrayList.forEach(ArrayList.java:1597)", + "org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)", + "org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)", + "java.base/java.util.ArrayList.forEach(ArrayList.java:1597)", + "org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:42)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$2(NodeTestTask.java:180)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$1(NodeTestTask.java:166)", + "org.junit.platform.engine.support.hierarchical.Node.around(Node.java:138)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$0(NodeTestTask.java:164)", + "org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:74)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:163)", + "org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:116)", + "org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:36)", + "org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:52)", + "org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:58)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.executeEngine(EngineExecutionOrchestrator.java:246)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.failOrExecuteEngine(EngineExecutionOrchestrator.java:218)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:179)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:66)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:157)", + "org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:65)", + "org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:125)", + "org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)", + "org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:93)", + "org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:48)", + "org.junit.platform.launcher.core.InterceptingLauncher.lambda$execute$0(InterceptingLauncher.java:41)", + "org.junit.platform.launcher.core.ClasspathAlignmentCheckingLauncherInterceptor.intercept(ClasspathAlignmentCheckingLauncherInterceptor.java:25)", + "org.junit.platform.launcher.core.InterceptingLauncher.execute(InterceptingLauncher.java:40)", + "org.junit.platform.launcher.core.DelegatingLauncher.execute(DelegatingLauncher.java:48)", + "org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:67)", + "com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)", + "com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)", + "com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)", + "com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)", + "com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:231)", + "com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)" + ], + "durationInNanos": 99998209 + } + ], + "casesAsTable": false, + "durationInNanos": 99998209 + } + ], + "tagMap": {} +} \ No newline at end of file diff --git a/jhotdraw-core/pom.xml b/jhotdraw-core/pom.xml index 7c276da85..6ec23a754 100644 --- a/jhotdraw-core/pom.xml +++ b/jhotdraw-core/pom.xml @@ -1,6 +1,21 @@ 4.0.0 + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.2.5 + + + --add-opens java.base/java.lang=ALL-UNNAMED + + + + + + org.jhotdraw jhotdraw @@ -9,6 +24,22 @@ jhotdraw-core jar + + + com.tngtech.jgiven + jgiven-junit5 + 1.3.0 + test + + + + + org.assertj + assertj-core + 3.24.2 + test + + ${project.groupId} jhotdraw-api @@ -40,5 +71,29 @@ jhotdraw-actions ${project.version} + + junit + junit + 4.13.2 + compile + + + org.junit.jupiter + junit-jupiter + RELEASE + compile + + + com.tngtech.jgiven + jgiven-junit5 + 1.3.0 + compile + + + org.assertj + assertj-core + 3.25.3 + compile + \ No newline at end of file diff --git a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/EllipseFigure.java b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/EllipseFigure.java index 624562ac9..26262d6fc 100644 --- a/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/EllipseFigure.java +++ b/jhotdraw-core/src/main/java/org/jhotdraw/draw/figure/EllipseFigure.java @@ -9,6 +9,7 @@ import java.awt.*; import java.awt.geom.*; +import org.jhotdraw.draw.AttributeKey; import org.jhotdraw.draw.AttributeKeys; import org.jhotdraw.draw.connector.ChopEllipseConnector; import org.jhotdraw.draw.connector.Connector; @@ -25,6 +26,29 @@ public class EllipseFigure extends AbstractAttributedFigure { private static final long serialVersionUID = 1L; protected Ellipse2D.Double ellipse; + /** + * Copy constructor for EllipseFigure. + */ + public EllipseFigure(EllipseFigure source) { + this.ellipse = copyEllipse(source.ellipse); + setAttributes(source.getAttributes()); + for (AttributeKey key : source.getAttributes().keySet()) { + if (!source.isAttributeEnabled(key)) { + setAttributeEnabled(key, false); + } + } + } + + + private Ellipse2D.Double copyEllipse(Ellipse2D.Double sourceEllipse) { + return new Ellipse2D.Double( + sourceEllipse.x, + sourceEllipse.y, + sourceEllipse.width, + sourceEllipse.height + ); + } + /** * Constructs a new {@code EllipseFigure}, initialized to * location (0, 0) and size (0, 0). @@ -77,14 +101,21 @@ public Rectangle2D.Double getDrawingArea() { return r; } + private Ellipse2D.Double prepareEllipse(double grow) { + Ellipse2D.Double copy = copyEllipse(this.ellipse); + copy.x -= grow; + copy.y -= grow; + copy.width += grow * 2; + copy.height += grow * 2; + return copy; + } + @Override protected void drawFill(Graphics2D g) { - Ellipse2D.Double r = (Ellipse2D.Double) ellipse.clone(); - double grow = AttributeKeys.getPerpendicularFillGrowth(this, AttributeKeys.getScaleFactorFromGraphics(g)); - r.x -= grow; - r.y -= grow; - r.width += grow * 2; - r.height += grow * 2; + Ellipse2D.Double r = prepareEllipse( + AttributeKeys.getPerpendicularFillGrowth(this, + AttributeKeys.getScaleFactorFromGraphics(g)) + ); if (r.width > 0 && r.height > 0) { g.fill(r); } @@ -92,28 +123,20 @@ protected void drawFill(Graphics2D g) { @Override protected void drawStroke(Graphics2D g) { - Ellipse2D.Double r = (Ellipse2D.Double) ellipse.clone(); - double grow = AttributeKeys.getPerpendicularDrawGrowth(this, AttributeKeys.getScaleFactorFromGraphics(g)); - r.x -= grow; - r.y -= grow; - r.width += grow * 2; - r.height += grow * 2; + Ellipse2D.Double r = prepareEllipse( + AttributeKeys.getPerpendicularDrawGrowth(this, + AttributeKeys.getScaleFactorFromGraphics(g)) + ); if (r.width > 0 && r.height > 0) { g.draw(r); } } - /** - * Checks if a Point2D.Double is inside the figure. - */ @Override public boolean contains(Point2D.Double p) { - Ellipse2D.Double r = (Ellipse2D.Double) ellipse.clone(); - double grow = AttributeKeys.getPerpendicularHitGrowth(this, 1.0); - r.x -= grow; - r.y -= grow; - r.width += grow * 2; - r.height += grow * 2; + Ellipse2D.Double r = prepareEllipse( + AttributeKeys.getPerpendicularHitGrowth(this, 1.0) + ); return r.contains(p); } @@ -139,13 +162,6 @@ public void transform(AffineTransform tx) { (Point2D.Double) tx.transform(lead, lead)); } - @Override - public EllipseFigure clone() { - EllipseFigure that = (EllipseFigure) super.clone(); - that.ellipse = (Ellipse2D.Double) this.ellipse.clone(); - return that; - } - @Override public void restoreTransformTo(Object geometry) { Ellipse2D.Double e = (Ellipse2D.Double) geometry; diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/EllipseFigureBDDTest.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/EllipseFigureBDDTest.java new file mode 100644 index 000000000..1beec7270 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/EllipseFigureBDDTest.java @@ -0,0 +1,16 @@ +package org.jhotdraw.draw.figure.BDDEllipse; + +import com.tngtech.jgiven.junit5.ScenarioTest; +import org.junit.jupiter.api.Test; + + +public class EllipseFigureBDDTest extends + ScenarioTest { + + @Test + public void ellipse_resizes_when_bounds_are_set() { + given().an_ellipse(); + when().bounds_are_set(0, 0, 100, 50); + then().ellipse_should_have_size(100, 50); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/GivenEllipse.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/GivenEllipse.java new file mode 100644 index 000000000..33e6e8909 --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/GivenEllipse.java @@ -0,0 +1,16 @@ +package org.jhotdraw.draw.figure.BDDEllipse; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import org.jhotdraw.draw.figure.EllipseFigure; + +public class GivenEllipse extends Stage { + + @ProvidedScenarioState + EllipseFigure ellipse; + + public GivenEllipse an_ellipse() { + ellipse = new EllipseFigure(); + return self(); + } +} diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/ThenEllipse.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/ThenEllipse.java new file mode 100644 index 000000000..019d1083c --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/ThenEllipse.java @@ -0,0 +1,20 @@ +package org.jhotdraw.draw.figure.BDDEllipse; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import org.jhotdraw.draw.figure.EllipseFigure; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ThenEllipse extends Stage { + + @ExpectedScenarioState + EllipseFigure ellipse; + + public ThenEllipse ellipse_should_have_size(double width, double height) { + assertThat(ellipse.getBounds().width).isEqualTo(width); + assertThat(ellipse.getBounds().height).isEqualTo(height); + return self(); + } +} + diff --git a/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/WhenSettingBounds.java b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/WhenSettingBounds.java new file mode 100644 index 000000000..ff28eeeef --- /dev/null +++ b/jhotdraw-core/src/test/java/org/jhotdraw/draw/figure/BDDEllipse/WhenSettingBounds.java @@ -0,0 +1,20 @@ +package org.jhotdraw.draw.figure.BDDEllipse; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import org.jhotdraw.draw.figure.EllipseFigure; + +import java.awt.geom.Point2D; + + +public class WhenSettingBounds extends Stage { + + @ExpectedScenarioState + EllipseFigure ellipse; + + public WhenSettingBounds bounds_are_set(double x1, double y1, double x2, double y2) { + ellipse.setBounds(new Point2D.Double(x1, y1), new Point2D.Double(x2, y2)); + return self(); + } +} + diff --git a/jhotdraw-gui/pom.xml b/jhotdraw-gui/pom.xml index 0c7a5da84..0539eff72 100644 --- a/jhotdraw-gui/pom.xml +++ b/jhotdraw-gui/pom.xml @@ -29,6 +29,74 @@ jhotdraw-core ${project.version} + + junit + junit + 4.13.2 + test + + + org.mockito + mockito-core + 5.12.0 + test + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + + org.yaml + snakeyaml + 2.2 + + + + org.junit.jupiter + junit-jupiter + RELEASE + test + + + + + com.tngtech.jgiven + jgiven-junit5 + 1.2.0 + test + + + + com.tngtech.jgiven + jgiven-core + 1.2.0 + test + + + + org.assertj + assertj-core + 3.24.2 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + jhotdraw-gui \ No newline at end of file diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/JFontChooser.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/JFontChooser.java index 7508a76e5..2f32ffb85 100644 --- a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/JFontChooser.java +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/JFontChooser.java @@ -128,7 +128,9 @@ public JFontChooser() { public void propertyChange(PropertyChangeEvent evt) { if ("ancestor".equals(evt.getPropertyName()) && evt.getNewValue() != null) { try { - ((DefaultFontChooserModel) model).setFonts(getAllFonts()); + Font[] fonts = getAllFonts(); + ((DefaultFontChooserModel) model).setFonts(fonts); + } catch (Exception ex) { ex.printStackTrace(); } @@ -255,7 +257,9 @@ protected void fireActionPerformed(String command) { Object[] listeners = listenerList.getListenerList(); long mostRecentEventTime = EventQueue.getMostRecentEventTime(); int modifiers = 0; + AWTEvent currentEvent = EventQueue.getCurrentEvent(); + if (currentEvent instanceof InputEvent) { modifiers = ((InputEvent) currentEvent).getModifiers(); } else if (currentEvent instanceof ActionEvent) { @@ -328,8 +332,10 @@ public Font[] call() throws Exception { //System.out.println("JFontChooser ***bogus*** "+decoded.getFontName()); } } + // set a default font so a font is always chosen. return goodFonts.toArray(new Font[goodFonts.size()]); // return fonts; + } }); new Thread(future).start(); @@ -351,6 +357,7 @@ public static synchronized Font[] getAllFonts() { } catch (InterruptedException | ExecutionException ex) { return new Font[0]; } + } /** @@ -376,12 +383,29 @@ public Font getSelectedFont() { * selected. */ public void setSelectedFont(Font newValue) { + if (!isAcceptableFont(newValue)) { + return; + } + Font oldValue = selectedFont; - this.selectedFont = newValue; + selectedFont = newValue; + firePropertyChange(SELECTED_FONT_PROPERTY, oldValue, newValue); updateSelectionPath(newValue); } + private boolean isAcceptableFont(Font font) { + if (font == null) { + return false; + } + + Font decoded = Font.decode(font.getFontName()); + String name = font.getFontName(); + + return decoded.getFontName().equals(name) + || decoded.getFontName().endsWith("-Derived"); + } + /** * Updates the selection path to the selected font. *

@@ -391,66 +415,86 @@ public void setSelectedFont(Font newValue) { * @param newValue */ protected void updateSelectionPath(Font newValue) { - if (newValue == null || selectionPath == null || selectionPath.getPathCount() != 4 - || !((FontFaceNode) selectionPath.getLastPathComponent()).getFont().getFontName().equals(newValue.getFontName())) { + if (shouldUpdateSelection(newValue)) { if (newValue == null) { setSelectionPath(null); } else { - TreePath path = selectionPath; - FontCollectionNode oldCollection = (path != null && path.getPathCount() > 1) ? (FontCollectionNode) path.getPathComponent(1) : null; - FontFamilyNode oldFamily = (path != null && path.getPathCount() > 2) ? (FontFamilyNode) path.getPathComponent(2) : null; - FontFaceNode oldFace = (path != null && path.getPathCount() > 3) ? (FontFaceNode) path.getPathComponent(3) : null; - FontCollectionNode newCollection = oldCollection; - FontFamilyNode newFamily = oldFamily; - FontFaceNode newFace = null; - // search in the current family - if (newFace == null && newFamily != null) { - for (FontFaceNode face : newFamily.faces()) { - if (face.getFont().getFontName().equals(newValue.getFontName())) { - newFace = face; - break; - } - } - } - // search in the current collection - if (newFace == null && newCollection != null) { - for (FontFamilyNode family : newCollection.families()) { - for (FontFaceNode face : family.faces()) { - if (face.getFont().getFontName().equals(newValue.getFontName())) { - newFamily = family; - newFace = face; - break; - } - } - } + searchFamily(newValue); + } + } + } + + private boolean shouldUpdateSelection(Font newValue) { + if (newValue == null || selectionPath == null || selectionPath.getPathCount() != 4) { + return true; + } + + FontFaceNode selected = + (FontFaceNode) selectionPath.getLastPathComponent(); + + return !selected.getFont().getFontName() + .equals(newValue.getFontName()); + } + + private void searchFamily(Font newValue) { + TreePath path = selectionPath; + + FontCollectionNode collection = + (path != null && path.getPathCount() > 1) + ? (FontCollectionNode) path.getPathComponent(1) + : null; + + FontFamilyNode family = + (path != null && path.getPathCount() > 2) + ? (FontFamilyNode) path.getPathComponent(2) + : null; + + FontFaceNode face = findFaceInFamily(family, newValue); + + if (face == null && collection != null) { + for (FontFamilyNode fam : collection.families()) { + face = findFaceInFamily(fam, newValue); + if (face != null) { + family = fam; + break; } - // search in all collections - if (newFace == null) { - TreeNode root = (TreeNode) getModel().getRoot(); - OuterLoop: - for (int i = 0, n = root.getChildCount(); i < n; i++) { - FontCollectionNode collection = (FontCollectionNode) root.getChildAt(i); - for (FontFamilyNode family : collection.families()) { - for (FontFaceNode face : family.faces()) { - if (face.getFont().getFontName().equals(newValue.getFontName())) { - newCollection = collection; - newFamily = family; - newFace = face; - break OuterLoop; - } - } - } + } + } + + if (face == null) { + Object root = getModel().getRoot(); + for (int i = 0; i < ((TreeNode) root).getChildCount(); i++) { + FontCollectionNode col = + (FontCollectionNode) ((TreeNode) root).getChildAt(i); + + for (FontFamilyNode fam : col.families()) { + face = findFaceInFamily(fam, newValue); + if (face != null) { + collection = col; + family = fam; + break; } } - if (newFace != null) { - setSelectionPath(new TreePath(new Object[]{ - getModel().getRoot(), newCollection, newFamily, newFace - })); - } else { - setSelectionPath(null); - } + if (face != null) break; + } + } + + setSelectionPath(face != null + ? new TreePath(new Object[]{getModel().getRoot(), collection, family, face}) + : null); + } + + private FontFaceNode findFaceInFamily(FontFamilyNode family, Font font) { + if (family == null) { + return null; + } + + for (FontFaceNode face : family.faces()) { + if (face.getFont().getFontName().equals(font.getFontName())) { + return face; } } + return null; } /** diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/DefaultFontChooserModel.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/DefaultFontChooserModel.java index 237443e7b..d21f20060 100644 --- a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/DefaultFontChooserModel.java +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/DefaultFontChooserModel.java @@ -8,9 +8,12 @@ package org.jhotdraw.gui.fontchooser; import java.awt.*; +import java.io.InputStream; import java.util.*; +import java.util.List; import javax.swing.tree.*; import org.jhotdraw.util.ResourceBundleUtil; +import org.yaml.snakeyaml.Yaml; /** * DefaultFontChooserModel with a predefined set of font collections. @@ -40,12 +43,17 @@ public class DefaultFontChooserModel extends AbstractFontChooserModel { */ protected DefaultMutableTreeNode root; + FontConfig config; + + public DefaultFontChooserModel() { root = new DefaultMutableTreeNode(); + config = FontConfigLoader.loadFontConfig(); } public DefaultFontChooserModel(Font[] fonts) { root = new DefaultMutableTreeNode(); + config = FontConfigLoader.loadFontConfig(); setFonts(fonts); } @@ -59,7 +67,50 @@ public DefaultFontChooserModel(Font[] fonts) { @SuppressWarnings("unchecked") public void setFonts(Font[] fonts) { ResourceBundleUtil labels = ResourceBundleUtil.getBundle("org.jhotdraw.gui.Labels"); - // collect families and sort them alphabetically + ArrayList families = getFamilyNodes(fonts, labels); + addOtherFontFamilies(labels, families); + fireTreeStructureChanged(this, new TreePath(root)); + } + + private void addOtherFontFamilies(ResourceBundleUtil labels, ArrayList families) { + FontCollectionNode others = new FontCollectionNode(labels.getString("FontCollection.other")); + HashSet otherFamilySet = new HashSet<>(); + otherFamilySet.addAll(families); + for (int i = 1, n = root.getChildCount(); i < n; i++) { + FontCollectionNode fcn = (FontCollectionNode) root.getChildAt(i); + for (FontFamilyNode ffn : fcn.families()) { + otherFamilySet.remove(ffn); + } + } + ArrayList otherFamilies = new ArrayList<>(); + for (FontFamilyNode ffn : otherFamilySet) { + otherFamilies.add(ffn.clone()); + } + Collections.sort(otherFamilies); + others.addAll(otherFamilies); + root.add(others); + } + + private ArrayList getFamilyNodes(Font[] fonts, ResourceBundleUtil labels) { + ArrayList families = getFontFamilyNodes(fonts, labels); + for (var entry : config.getGroups().entrySet()) { + String groupKey = entry.getKey(); + FontGroup group = entry.getValue(); + + List fontNames = group.getFonts(); + String labelKey = group.getLabel(); + + root.add( + new FontCollectionNode( + labels.getString(labelKey), + collectFamiliesNamed(families, fontNames) + ) + ); + } + return families; + } + + private ArrayList getFontFamilyNodes(Font[] fonts, ResourceBundleUtil labels) { ArrayList families = new ArrayList<>(); HashMap familyMap = new HashMap<>(); for (Font f : fonts) { @@ -78,432 +129,13 @@ public void setFonts(Font[] fonts) { // group families into collections root.removeAllChildren(); root.add(new FontCollectionNode(labels.getString("FontCollection.allFonts"), (ArrayList) families.clone())); - // Web-save fonts - root.add( - new FontCollectionNode(labels.getString("FontCollection.web"), collectFamiliesNamed(families, - "Arial", - "Arial Black", - "Comic Sans MS", - "Georgia", - "Impact", - "Times New Roman", - "Trebuchet MS", - "Verdana", - "Webdings"))); - /* - // PDF Fonts - root.add( - new FontCollectionNode(labels.getString("FontCollection.pdf"), collectFamiliesNamed(families, - "Andale Mono", - "Courier", - "Helvetica", - "Symbol", - "Times", - "Zapf Dingbats"))); - */ - // Java System fonts - root.add( - new FontCollectionNode(labels.getString("FontCollection.system"), collectFamiliesNamed(families, - "Dialog", - "DialogInput", - "Monospaced", - "SansSerif", - "Serif"))); - // Serif fonts - root.add( - new FontCollectionNode(labels.getString("FontCollection.serif"), collectFamiliesNamed(families, - // Fonts on Mac OS X 10.5: - "Adobe Caslon Pro", - "Adobe Garamond Pro", - "American Typewriter", - "Arno Pro", - "Baskerville", - "Baskerville Old Face", - "Bell MT", - "Big Caslon", - "Bodoni SvtyTwo ITC TT", - "Bodoni SvtyTwo OS ITC TT", - "Bodoni SvtyTwo SC ITC TT", - "Book Antiqua", - "Bookman Old Style", - "Calisto MT", - "Chaparral Pro", - "Century", - "Century Schoolbook", - "Cochin", - "Footlight MT Light", - "Garamond", - "Garamond Premier Pro", - "Georgia", - "Goudy Old Style", - "Hoefler Text", - "Lucida Bright", - "Lucida Fax", - "Minion Pro", - "Palatino", - "Times", - "Times New Roman", - // Fonts on Mac OS X 10.6: - "Didot", - // Fonts on Windows XP: - "Palatino Linotype", - "Bitstream Vera Serif Bold", - "Bodoni MT", - "Bodoni MT Black", - "Bodoni MT Condensed", - "Californian FB", - "Cambria", - "Cambria Math", - "Centaur", - "Constantia", - "High Tower Text", - "Perpetua", - "Poor Richard", - "Rockwell Condensed", - "Slimbach-Black", - "Slimbach-BlackItalic", - "Slimbach-Bold", - "Slimbach-BoldItalic", - "Slimbach-Book", - "Slimbach-BookItalic", - "Slimbach-Medium", - "Slimbach-MediumItalic", - "Sylfaen", - // Fonts on Windows Vista - "Andalus", - "Angsana New", - "AngsanaUPC", - "Arabic Typesetting", - "Cambria", - "Cambria Math", - "Constantia", - "DaunPenh", - "David", - "DilleniaUPC", - "EucrosiaUPC", - "Frank Ruehl", - "IrisUPC", - "Iskoola Pota", - "JasmineUPC", - "KodchiangUPC", - "Narkisim"))); - // Sans Serif - root.add( - new FontCollectionNode(labels.getString("FontCollection.sansSerif"), collectFamiliesNamed(families, - // Fonts on Mac OS X 10.5: - "Abadi MT Condensed Extra Bold", - "Abadi MT Condensed Light", - "AppleGothic", - "Arial", - "Arial Black", - "Arial Narrow", - "Arial Rounded MT Bold", - "Arial Unicode MS", - "Bell Gothic Std", - "Blair MdITC TT", - "Century Gothic", - "Frutiger", - "Futura", - "Geneva", - "Gill Sans", - "Gulim", - "Helvetica", - "Helvetica Neue", - "Lucida Grande", - "Lucida Sans", - "Microsoft Sans Serif", - "Myriad Pro", - "News Gothic", - "Tahoma", - "Trebuchet MS", - "Verdana", - // Fonts on Mac OS X 10.6: - "Charcoal", - "Euphemia UCAS", - // Fonts on Windows XP: - "Franklin Gothic Medium", - "Lucida Sans Unicode", - "Agency FB", - "Berlin Sans FB", - "Berlin Sans FB Demi Bold", - "Bitstream Vera Sans Bold", - "Calibri", - "Candara", - "Corbel", - "Estrangelo Edessa", - "Eras Bold ITC", - "Eras Demi ITC", - "Eras Light ITC", - "Eras Medium ITC", - "Franklin Gothic Book", - "Franklin Gothic Demi", - "Franklin Gothic Demi Cond", - "Franklin Gothic Heavy", - "Franklin Gothic Medium Cond", - "Gill Sans MT", - "Gill Sans MT Condensed", - "Gill Sans MT Ext Condensed Bold", - "Maiandra GD", - "MS Reference Sans...", - "Tw Cen MT", - "Tw Cen MT Condensed", - "Tw Cen MT Condensed Extra Bold", - // Fonts on Windows Vista: - "Aharoni", - "Browallia New", - "BrowalliaUPC", - "Calibri", - "Candara", - "Corbel", - "Cordia New", - "CordiaUPC", - "DokChampa", - "Dotum", - "Estrangelo Edessa", - "Euphemia", - "Freesia UPC", - "Gautami", - "Gisha", - "Kalinga", - "Kartika", - "Levenim MT", - "LilyUPC", - "Malgun Gothic", - "Meiryo", - "Miriam", - "Segoe UI"))); - // Scripts - root.add( - new FontCollectionNode(labels.getString("FontCollection.script"), collectFamiliesNamed(families, - // Fonts on Mac OS X 10.5: - "Apple Chancery", - "Bickham Script Pro", - "Blackmoor LET", - "Bradley Hand ITC TT", - "Brush Script MT", - "Brush Script Std", - "Chalkboard", - "Charlemagne Std", - "Comic Sans MS", - "Curlz MT", - "Edwardian Script ITC", - "Footlight MT Light", - "Giddyup Std", - "Handwriting - Dakota", - "Harrington", - "Herculanum", - "Kokonor", - "Lithos Pro", - "Lucida Blackletter", - "Lucida Calligraphy", - "Lucida Handwriting", - "Marker Felt", - "Matura MT Script Capitals", - "Mistral", - "Monotype Corsiva", - "Party LET", - "Papyrus", - "Santa Fe LET", - "Savoye LET", - "SchoolHouse Cursive B", - "SchoolHouse Printed A", - "Skia", - "Snell Roundhand", - "Tekton Pro", - "Trajan Pro", - "Zapfino", - // Fonts on Mac OS X 10.6: - "Casual", - "Chalkduster", - // Fonts on Windows XP: - "Blackadder ITC", - "Bradley Hand ITC", - "Chiller", - "Freestyle Script", - "French Script MT", - "Gigi", - "Harlow Solid Italic", - "Informal Roman", - "Juice ITC", - "Kristen ITC", - "Kunstler Script", - "Magneto Bold", - "Maiandra GD", - "Old English Text", - "Palace Script MT", - "Parchment", - "Pristina", - "Rage Italic", - "Ravie", - "Script MT Bold", - "Tempus Sans ITC", - "Viner Hand ITC", - "Vivaldi Italic", - "Vladimir Script", - // Fonts on Windows Vista - "Segoe Print", - "Segoe Script"))); - // Monospaced - root.add( - new FontCollectionNode(labels.getString("FontCollection.monospaced"), collectFamiliesNamed(families, - // Fonts on Mac OS X 10.5: - "Andale Mono", - "Courier", - "Courier New", - "Letter Gothic Std", - "Lucida Sans Typewriter", - "Monaco", - "OCR A Std", - "Orator Std", - "Prestige Elite Std", - // Fonts on Mac OS X 10.6: - "Menlo", - // Fonts on Windows XP: - "Lucida Console", - "Bitstream Vera S...", - "Consolas", - "OCR A Extended", - "OCR B", - // Fonts on Windows Vista - "Consolas", - "DotumChe", - "Miriam Fixed", - "Rod"))); - // Decorative - root.add( - new FontCollectionNode(labels.getString("FontCollection.decorative"), collectFamiliesNamed(families, - // Fonts on Mac OS X 10.5: - "Academy Engraved LET", - "Arial Black", - "Bank Gothic", - "Bauhaus 93", - "Bernard MT Condensed", - "Birch Std", - "Blackoak Std", - "BlairMdITC TT", - "Bordeaux Roman Bold LET", - "Braggadocio", - "Britannic Bold", - "Capitals", - "Colonna MT", - "Cooper Black", - "Cooper Std", - "Copperplate", - "Copperplate Gothic Bold", - "Copperplate Gothic Light", - "Cracked", - "Desdemona", - "Didot", - "Eccentric Std", - "Engravers MT", - "Eurostile", - "Gill Sans Ultra Bold", - "Gloucester MT Extra Condensed", - "Haettenschweiler", - "Hobo Std", - "Impact", - "Imprint MT Shadow", - "Jazz LET", - "Kino MT", - "Matura MT Script Capitals", - "Mesquite Std", - "Modern No. 20", - "Mona Lisa Solid ITC TT", - "MS Gothic", - "Nueva Std", - "Onyx", - "Optima", - "Perpetua Titling MT", - "Playbill", - "Poplar Std", - "PortagoITC TT", - "Princetown LET", - "Rockwell", - "Rockwell Extra Bold", - "Rosewood Std", - "Santa Fe LET", - "Stencil", - "Stencil Std", - "Stone Sans ITC TT", - "Stone Sans OS ITC TT", - "Stone Sans Sem ITC TT", - "Stone Sans Sem OS ITCTT", - "Stone Sans Sem OS ITC TT", - "Synchro LET", - "Wide Latin", - // Fonts on Mac OS X 10.5: - "HeadLineA", - // Fonts on Windows XP: - "Algerian", - "Bodoni MT Black", - "Bodoni MT Poster Compressed", - "Broadway", - "Castellar", - "Elephant", - "Felix Titling", - "Franklin Gothic Heavy", - "Gill Sans MT Ext Condensed Bold", - "Gill Sans Ultra Bold Condensed", - "Goudy Stout", - "Jokerman", - "Juice ITC", - "Magneto", - "Magneto Bold", - "Niagara Engraved", - "Niagara Solid", - "Poor Richard", - "Ravie", - "Rockwell Condensed", - "Showcard Gothic", - "Slimbach-Black", - "Slimbach-BlackItalic", - "Snap ITC" // Fonts on Windows Vista: - ))); - root.add( - new FontCollectionNode(labels.getString("FontCollection.symbols"), collectFamiliesNamed(families, - // Fonts on Mac OS X 10.5: - "Apple Symbols", - "Blackoack Std", - "Bodoni Ornaments ITC TT", - "EuropeanPi", - "Monotype Sorts", - "MT Extra", - "Symbol", - "Type Embellishments One LET", - "Webdings", - "Wingdings", - "Wingdings 2", - "Wingdings 3", - "Zapf Dingbats", - // Fonts on Windows XP: - "Bookshelf Symbol" - // Fonts on Windows Vista: - ))); - // Collect font families, which are not in one of the other collections - // (except the collection AllFonts). - FontCollectionNode others = new FontCollectionNode(labels.getString("FontCollection.other")); - HashSet otherFamilySet = new HashSet<>(); - otherFamilySet.addAll(families); - for (int i = 1, n = root.getChildCount(); i < n; i++) { - FontCollectionNode fcn = (FontCollectionNode) root.getChildAt(i); - for (FontFamilyNode ffn : fcn.families()) { - otherFamilySet.remove(ffn); - } - } - ArrayList otherFamilies = new ArrayList<>(); - for (FontFamilyNode ffn : otherFamilySet) { - otherFamilies.add(ffn.clone()); - } - Collections.sort(otherFamilies); - others.addAll(otherFamilies); - root.add(others); - fireTreeStructureChanged(this, new TreePath(root)); + return families; } - protected ArrayList collectFamiliesNamed(ArrayList families, String... names) { + protected ArrayList collectFamiliesNamed(ArrayList families, List names) { ArrayList coll = new ArrayList<>(); HashSet nameMap = new HashSet<>(); - nameMap.addAll(Arrays.asList(names)); + nameMap.addAll(names); for (FontFamilyNode family : families) { String fName = family.getName(); if (nameMap.contains(family.getName())) { @@ -561,6 +193,10 @@ public int getIndexOfChild(Object parent, Object child) { return ((TreeNode) parent).getIndex((TreeNode) child); } + public FontConfig getConfig() { + return config; + } + public static class UIResource extends DefaultFontChooserModel implements javax.swing.plaf.UIResource { } } diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontConfig.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontConfig.java new file mode 100644 index 000000000..2c7898bac --- /dev/null +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontConfig.java @@ -0,0 +1,18 @@ +package org.jhotdraw.gui.fontchooser; + +import java.util.Map; + +public class FontConfig { + private Map groups; + + public FontConfig() { + } + + public Map getGroups() { + return groups; + } + + public void setGroups(Map groups) { + this.groups = groups; + } +} diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontConfigLoader.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontConfigLoader.java new file mode 100644 index 000000000..d2d8469d3 --- /dev/null +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontConfigLoader.java @@ -0,0 +1,20 @@ +package org.jhotdraw.gui.fontchooser; + +import org.yaml.snakeyaml.Yaml; + +import java.awt.*; +import java.io.InputStream; + +public class FontConfigLoader { + public static FontConfig loadFontConfig() { + InputStream in = FontConfig.class.getResourceAsStream("/org/jhotdraw/font/font-groups.yml"); + + if (in == null) { + throw new IllegalStateException("font-groups.yml not found"); + } + + Yaml yaml = new Yaml(); + FontConfig config = yaml.loadAs(in, FontConfig.class); + return config; + } +} diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontFaceNode.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontFaceNode.java index d7f1f2a9d..75bae708d 100644 --- a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontFaceNode.java +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontFaceNode.java @@ -10,6 +10,7 @@ import java.awt.Font; import java.util.Collections; import java.util.Enumeration; +import java.util.Map; import javax.swing.tree.MutableTreeNode; import javax.swing.tree.TreeNode; @@ -30,55 +31,47 @@ public FontFaceNode(Font typeface) { this.name = beautifyName(typeface.getPSName()); } + protected String capitalize(String s) { + if (s == null || s.isEmpty()) { + return s; + } + String c = String.valueOf(s.charAt(0)); + c = c.toUpperCase(); + s = s.substring(1); + s = c.concat(s); + return s; + } + protected String beautifyName(String name) { // 'Beautify' the name int p = name.lastIndexOf('-'); if (p != -1) { name = name.substring(p + 1); - String lcName = name.toLowerCase(); - if ("plain".equals(lcName)) { - name = "Plain"; - } else if ("bolditalic".equals(lcName)) { - name = "Bold Italic"; - } else if ("italic".equals(lcName)) { - name = "Italic"; - } else if ("bold".equals(lcName)) { - name = "Bold"; - } - } else { - String lcName = name.toLowerCase(); - if (lcName.endsWith("plain")) { - name = "Plain"; - } else if (lcName.endsWith("boldoblique")) { - name = "Bold Oblique"; - } else if (lcName.endsWith("bolditalic")) { - name = "Bold Italic"; - } else if (lcName.endsWith("bookita")) { - name = "Book Italic"; - } else if (lcName.endsWith("bookit")) { - name = "Book Italic"; - } else if (lcName.endsWith("demibold")) { - name = "Demi Bold"; - } else if (lcName.endsWith("semiita")) { - name = "Semi Italic"; - } else if (lcName.endsWith("italic")) { - name = "Italic"; - } else if (lcName.endsWith("book")) { - name = "Book"; - } else if (lcName.endsWith("bold")) { - name = "Bold"; - } else if (lcName.endsWith("bol")) { - name = "Bold"; - } else if (lcName.endsWith("oblique")) { - name = "Oblique"; - } else if (lcName.endsWith("regular")) { - name = "Regular"; - } else if (lcName.endsWith("semi")) { - name = "Semi"; - } else { - name = "Plain"; + } + + String lcName = name.toLowerCase(); + name = capitalize(lcName); + + Map replacements = Map.of( + "bolditalic", "Bold Italic", + "boldoblique", "Bold Oblique", + "bookita", "Book Italic", + "bookit", "Book Italic", + "demibold", "Demi Bold", + "semiita", "Semi Italic" + ); + + for (Map.Entry entry : replacements.entrySet()) { + if (lcName.endsWith(entry.getKey())) { + return entry.getValue(); } } + + name = insertSpaces(name); + return name; + } + + private static String insertSpaces(String name) { StringBuilder buf = new StringBuilder(); char prev = name.charAt(0); buf.append(prev); diff --git a/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontGroup.java b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontGroup.java new file mode 100644 index 000000000..2bb903345 --- /dev/null +++ b/jhotdraw-gui/src/main/java/org/jhotdraw/gui/fontchooser/FontGroup.java @@ -0,0 +1,36 @@ +package org.jhotdraw.gui.fontchooser; + +import java.util.List; + +public class FontGroup { + + String label; + List listOfFonts; + + public FontGroup() { + } + + + public FontGroup(String labelKey, List listOfFonts) { + this.label = labelKey; + this.listOfFonts = listOfFonts; + } + + public String getLabel() { + return label; + + } + + public void setLabel(String labelKey) { + this.label = labelKey; + } + + public List getFonts() { + return listOfFonts; + } + + public void setFonts(List listOfFonts) { + this.listOfFonts = listOfFonts; + } + +} diff --git a/jhotdraw-gui/src/main/resources/org/jhotdraw/font/font-groups.yml b/jhotdraw-gui/src/main/resources/org/jhotdraw/font/font-groups.yml new file mode 100644 index 000000000..71e2010c7 --- /dev/null +++ b/jhotdraw-gui/src/main/resources/org/jhotdraw/font/font-groups.yml @@ -0,0 +1,373 @@ +groups: + + allFonts: + label: FontCollection.allFonts + fonts: [] + + web: + label: FontCollection.web + fonts: + - Arial + - Arial Black + - Comic Sans MS + - Georgia + - Impact + - Times New Roman + - Trebuchet MS + - Verdana + - Webdings + + system: + label: FontCollection.system + fonts: + - Dialog + - DialogInput + - Monospaced + - SansSerif + - Serif + + serif: + label: FontCollection.serif + fonts: + - Adobe Caslon Pro + - Adobe Garamond Pro + - American Typewriter + - Arno Pro + - Baskerville + - Baskerville Old Face + - Bell MT + - Big Caslon + - Bodoni SvtyTwo ITC TT + - Bodoni SvtyTwo OS ITC TT + - Bodoni SvtyTwo SC ITC TT + - Book Antiqua + - Bookman Old Style + - Calisto MT + - Chaparral Pro + - Century + - Century Schoolbook + - Cochin + - Footlight MT Light + - Garamond + - Garamond Premier Pro + - Georgia + - Goudy Old Style + - Hoefler Text + - Lucida Bright + - Lucida Fax + - Minion Pro + - Palatino + - Times + - Times New Roman + - Didot + - Palatino Linotype + - Bitstream Vera Serif Bold + - Bodoni MT + - Bodoni MT Black + - Bodoni MT Condensed + - Californian FB + - Cambria + - Cambria Math + - Centaur + - Constantia + - High Tower Text + - Perpetua + - Poor Richard + - Rockwell Condensed + - Slimbach-Black + - Slimbach-BlackItalic + - Slimbach-Bold + - Slimbach-BoldItalic + - Slimbach-Book + - Slimbach-BookItalic + - Slimbach-Medium + - Slimbach-MediumItalic + - Sylfaen + - Andalus + - Angsana New + - AngsanaUPC + - Arabic Typesetting + - DaunPenh + - David + - DilleniaUPC + - EucrosiaUPC + - Frank Ruehl + - IrisUPC + - Iskoola Pota + - JasmineUPC + - KodchiangUPC + - Narkisim + + sansSerif: + label: FontCollection.sansSerif + fonts: + - Abadi MT Condensed Extra Bold + - Abadi MT Condensed Light + - AppleGothic + - Arial + - Arial Black + - Arial Narrow + - Arial Rounded MT Bold + - Arial Unicode MS + - Bell Gothic Std + - Blair MdITC TT + - Century Gothic + - Frutiger + - Futura + - Geneva + - Gill Sans + - Gulim + - Helvetica + - Helvetica Neue + - Lucida Grande + - Lucida Sans + - Microsoft Sans Serif + - Myriad Pro + - News Gothic + - Tahoma + - Trebuchet MS + - Verdana + - Charcoal + - Euphemia UCAS + - Franklin Gothic Medium + - Lucida Sans Unicode + - Agency FB + - Berlin Sans FB + - Berlin Sans FB Demi Bold + - Bitstream Vera Sans Bold + - Calibri + - Candara + - Corbel + - Estrangelo Edessa + - Eras Bold ITC + - Eras Demi ITC + - Eras Light ITC + - Eras Medium ITC + - Franklin Gothic Book + - Franklin Gothic Demi + - Franklin Gothic Demi Cond + - Franklin Gothic Heavy + - Franklin Gothic Medium Cond + - Gill Sans MT + - Gill Sans MT Condensed + - Gill Sans MT Ext Condensed Bold + - Maiandra GD + - MS Reference Sans... + - Tw Cen MT + - Tw Cen MT Condensed + - Tw Cen MT Condensed Extra Bold + - Aharoni + - Browallia New + - BrowalliaUPC + - Cordia New + - CordiaUPC + - DokChampa + - Dotum + - Euphemia + - Freesia UPC + - Gautami + - Gisha + - Kalinga + - Kartika + - Levenim MT + - LilyUPC + - Malgun Gothic + - Meiryo + - Miriam + - Segoe UI + + script: + label: FontCollection.script + fonts: + - Apple Chancery + - Bickham Script Pro + - Blackmoor LET + - Bradley Hand ITC TT + - Brush Script MT + - Brush Script Std + - Chalkboard + - Charlemagne Std + - Comic Sans MS + - Curlz MT + - Edwardian Script ITC + - Footlight MT Light + - Giddyup Std + - Handwriting - Dakota + - Harrington + - Herculanum + - Kokonor + - Lithos Pro + - Lucida Blackletter + - Lucida Calligraphy + - Lucida Handwriting + - Marker Felt + - Matura MT Script Capitals + - Mistral + - Monotype Corsiva + - Party LET + - Papyrus + - Santa Fe LET + - Savoye LET + - SchoolHouse Cursive B + - SchoolHouse Printed A + - Skia + - Snell Roundhand + - Tekton Pro + - Trajan Pro + - Zapfino + - Casual + - Chalkduster + - Blackadder ITC + - Bradley Hand ITC + - Chiller + - Freestyle Script + - French Script MT + - Gigi + - Harlow Solid Italic + - Informal Roman + - Juice ITC + - Kristen ITC + - Kunstler Script + - Magneto Bold + - Maiandra GD + - Old English Text + - Palace Script MT + - Parchment + - Pristina + - Rage Italic + - Ravie + - Script MT Bold + - Tempus Sans ITC + - Viner Hand ITC + - Vivaldi Italic + - Vladimir Script + - Segoe Print + - Segoe Script + + monospaced: + label: FontCollection.monospaced + fonts: + - Andale Mono + - Courier + - Courier New + - Letter Gothic Std + - Lucida Sans Typewriter + - Monaco + - OCR A Std + - Orator Std + - Prestige Elite Std + - Menlo + - Lucida Console + - Bitstream Vera S... + - Consolas + - OCR A Extended + - OCR B + - DotumChe + - Miriam Fixed + - Rod + + decorative: + label: FontCollection.decorative + fonts: + - Academy Engraved LET + - Arial Black + - Bank Gothic + - Bauhaus 93 + - Bernard MT Condensed + - Birch Std + - Blackoak Std + - BlairMdITC TT + - Bordeaux Roman Bold LET + - Braggadocio + - Britannic Bold + - Capitals + - Colonna MT + - Cooper Black + - Cooper Std + - Copperplate + - Copperplate Gothic Bold + - Copperplate Gothic Light + - Cracked + - Desdemona + - Didot + - Eccentric Std + - Engravers MT + - Eurostile + - Gill Sans Ultra Bold + - Gloucester MT Extra Condensed + - Haettenschweiler + - Hobo Std + - Impact + - Imprint MT Shadow + - Jazz LET + - Kino MT + - Matura MT Script Capitals + - Mesquite Std + - Modern No. 20 + - Mona Lisa Solid ITC TT + - MS Gothic + - Nueva Std + - Onyx + - Optima + - Perpetua Titling MT + - Playbill + - Poplar Std + - PortagoITC TT + - Princetown LET + - Rockwell + - Rockwell Extra Bold + - Rosewood Std + - Santa Fe LET + - Stencil + - Stencil Std + - Stone Sans ITC TT + - Stone Sans OS ITC TT + - Stone Sans Sem ITC TT + - Stone Sans Sem OS ITCTT + - Stone Sans Sem OS ITC TT + - Synchro LET + - Wide Latin + - HeadLineA + - Algerian + - Bodoni MT Black + - Bodoni MT Poster Compressed + - Broadway + - Castellar + - Elephant + - Felix Titling + - Franklin Gothic Heavy + - Gill Sans MT Ext Condensed Bold + - Gill Sans Ultra Bold Condensed + - Goudy Stout + - Jokerman + - Juice ITC + - Magneto + - Magneto Bold + - Niagara Engraved + - Niagara Solid + - Poor Richard + - Ravie + - Rockwell Condensed + - Showcard Gothic + - Slimbach-Black + - Slimbach-BlackItalic + - Snap ITC + + symbols: + label: FontCollection.symbols + fonts: + - Apple Symbols + - Blackoack Std + - Bodoni Ornaments ITC TT + - EuropeanPi + - Monotype Sorts + - MT Extra + - Symbol + - Type Embellishments One LET + - Webdings + - Wingdings + - Wingdings 2 + - Wingdings 3 + - Zapf Dingbats + - Bookshelf Symbol + diff --git a/jhotdraw-gui/src/main/test/fontTests/BDDTests.java b/jhotdraw-gui/src/main/test/fontTests/BDDTests.java new file mode 100644 index 000000000..70b5fd7d1 --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/BDDTests.java @@ -0,0 +1,20 @@ +package fontTests; + + +import com.tngtech.jgiven.junit5.ScenarioTest; +import org.junit.jupiter.api.Test; + +import java.awt.*; + +public class BDDTests extends ScenarioTest { + + private Font font1 = new Font("Arial", Font.PLAIN, 12); + + @Test + public void the_font_should_be_chosen() { + given().the_font(); + when().choosing_font(font1); + then().the_font_should_be_chosen(font1); + } + +} diff --git a/jhotdraw-gui/src/main/test/fontTests/DefaultFontChooserModelTests.java b/jhotdraw-gui/src/main/test/fontTests/DefaultFontChooserModelTests.java new file mode 100644 index 000000000..5333991fc --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/DefaultFontChooserModelTests.java @@ -0,0 +1,48 @@ +package fontTests; + +import org.jhotdraw.gui.JFontChooser; +import org.jhotdraw.gui.fontchooser.DefaultFontChooserModel; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; + +import static org.junit.Assert.*; + +public class DefaultFontChooserModelTests { + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + + @Test + // Invariant test + // Regardless of which fonts are set, the models root node has the amount of children as there are groups in the yaml. + public void setFontsTest() { + DefaultFontChooserModel model = new DefaultFontChooserModel(); + int expected = 1 + model.getConfig().getGroups().size() + 1; + + model.setFonts(new Font[] { new Font("Arial", Font.PLAIN, 12) }); + Assertions.assertEquals(expected, model.getChildCount(model.getRoot())); + + model.setFonts(GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts()); + assertEquals(expected, model.getChildCount(model.getRoot())); + + model.setFonts(new Font[] { new Font("Times New Roman", Font.BOLD, 14) }); + assertEquals(expected, model.getChildCount(model.getRoot())); + + + } + + + +} diff --git a/jhotdraw-gui/src/main/test/fontTests/FontFaceNodeTests.java b/jhotdraw-gui/src/main/test/fontTests/FontFaceNodeTests.java new file mode 100644 index 000000000..3b061d66a --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/FontFaceNodeTests.java @@ -0,0 +1,48 @@ +package fontTests; + +import org.jhotdraw.gui.fontchooser.FontFaceNode; +import org.junit.Test; +import org.junit.jupiter.api.BeforeEach; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionListener; + +import static org.junit.Assert.*; + +public class FontFaceNodeTests { + + @Mock + private ActionListener listener; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + @Test + // Best case scenario + public void beautifyNameTest() { + Font testFont = new Font("Dialog", Font.PLAIN, 12); + FontFaceNode faceNode = new FontFaceNode(testFont); + assertEquals("Dialog.plain", faceNode.toString()); + } + + // Test boundary case scenario for FontFaceNode + @Test + public void beautifyNameTestBoundaryCase() { + Font testFont = new Font("", Font.PLAIN, 12); + FontFaceNode faceNode = new FontFaceNode(testFont); + assertEquals("Dialog.plain", faceNode.toString()); + } + + // Invariant test for fontfacenode. Must always be a leaf. + @Test + public void isLeafTest() { + FontFaceNode node = new FontFaceNode(new Font("Arial", 0, 12)); + assertTrue(node.isLeaf()); + assertEquals(0, node.getChildCount()); + } +} diff --git a/jhotdraw-gui/src/main/test/fontTests/GivenFont.java b/jhotdraw-gui/src/main/test/fontTests/GivenFont.java new file mode 100644 index 000000000..3da855b78 --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/GivenFont.java @@ -0,0 +1,21 @@ +package fontTests; + +import com.tngtech.jgiven.Stage; +import com.tngtech.jgiven.annotation.ProvidedScenarioState; +import org.jhotdraw.gui.JFontChooser; + +import java.awt.*; + +public class GivenFont extends Stage { + + @ProvidedScenarioState + JFontChooser fontChooser; + public Font font; + + public GivenFont the_font() { + fontChooser = new JFontChooser(); + font = new Font("Arial", Font.PLAIN, 12); + return self(); + } + +} diff --git a/jhotdraw-gui/src/main/test/fontTests/JFontChooserTests.java b/jhotdraw-gui/src/main/test/fontTests/JFontChooserTests.java new file mode 100644 index 000000000..c0802c6c0 --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/JFontChooserTests.java @@ -0,0 +1,110 @@ +package fontTests; + + +import org.jhotdraw.gui.JFontChooser; +import org.jhotdraw.gui.fontchooser.DefaultFontChooserModel; +import org.jhotdraw.gui.fontchooser.FontFaceNode; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import javax.swing.*; +import javax.swing.tree.TreePath; +import java.awt.*; +import java.awt.event.ActionListener; +import java.util.concurrent.atomic.AtomicReference; + +import static org.junit.Assert.*; + +public class JFontChooserTests { + + @Mock + private ActionListener listener; + private Font font1 = new Font("Arial", Font.PLAIN, 12); + private Font font2 = new Font("Times New Roman", Font.BOLD, 14); + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + } + + + @Test + public void addAndRemoveActionListener() { + JFontChooser fontChooser = new JFontChooser(); + + fontChooser.addActionListener(listener); + assertEquals(1, fontChooser.getListeners(ActionListener.class).length); + fontChooser.removeActionListener(listener); + assertEquals(0, fontChooser.getListeners(ActionListener.class).length); + + + } + + // Test that font is always chosen. One font must always be chosen, that is the invariant. + @Test + public void chooseFont() { + JFontChooser fontChooser = new JFontChooser(); + + fontChooser.setSelectedFont(font1); + assertEquals(font1, fontChooser.getSelectedFont()); + + fontChooser.setSelectedFont(font2); + assertEquals(font2, fontChooser.getSelectedFont()); + } + + @Test + public void chooseFakeFont() { + JFontChooser fontChooser = new JFontChooser(); + + Font font3 = new Font("BOGUSA AND FAKE FONT, NOT REAL 1293129391239###!!!!", Font.BOLD, 999999999); + + fontChooser.setSelectedFont(font1); + assertEquals(font1, fontChooser.getSelectedFont()); + + fontChooser.setSelectedFont(font3); + assertEquals(font3, fontChooser.getSelectedFont()); + + } + + + // Test the invariant: approveSelection should always fire the correct command. + @Test + public void approveSelectionTest() { + JFontChooser chooser = new JFontChooser(); + AtomicReference command = new AtomicReference<>(); + + chooser.addActionListener(e -> command.set(e.getActionCommand())); + + chooser.approveSelection(); + + assertEquals(JFontChooser.APPROVE_SELECTION, command.get()); + } + + @Test + void updateSelectionPath_setsCorrectTreePath_whenFontExists() { + // Arrange + JFontChooser chooser = new JFontChooser(); + + Font font = new Font("Arial", Font.PLAIN, 12); + Font[] fonts = new Font[] { font }; + + DefaultFontChooserModel model = new DefaultFontChooserModel(fonts); + chooser.setModel(model); + + chooser.setSelectedFont(font); + + TreePath selectionPath = chooser.getSelectionPath(); + assertNotNull(selectionPath); + + Object lastComponent = selectionPath.getLastPathComponent(); + assertTrue(lastComponent instanceof FontFaceNode); + + FontFaceNode faceNode = (FontFaceNode) lastComponent; + assertEquals(font.getFontName(), faceNode.getFont().getFontName()); + } + +} diff --git a/jhotdraw-gui/src/main/test/fontTests/ThenFontChosen.java b/jhotdraw-gui/src/main/test/fontTests/ThenFontChosen.java new file mode 100644 index 000000000..50fea3822 --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/ThenFontChosen.java @@ -0,0 +1,26 @@ +package fontTests; + +import com.tngtech.jgiven.Stage; + + +import com.tngtech.jgiven.annotation.ExpectedScenarioState; +import org.jhotdraw.gui.JFontChooser; + + +import static org.assertj.core.api.Assertions.assertThat; + +import java.awt.*; + +public class ThenFontChosen extends Stage { + + @ExpectedScenarioState + Font font; + @ExpectedScenarioState + JFontChooser fontChooser; + public ThenFontChosen the_font_should_be_chosen(Font font) { + assertThat(fontChooser.getFont()).isEqualTo(font); + return this; + } + + +} diff --git a/jhotdraw-gui/src/main/test/fontTests/WhenChoosingFont.java b/jhotdraw-gui/src/main/test/fontTests/WhenChoosingFont.java new file mode 100644 index 000000000..6fc4224f1 --- /dev/null +++ b/jhotdraw-gui/src/main/test/fontTests/WhenChoosingFont.java @@ -0,0 +1,23 @@ +package fontTests; + +import com.tngtech.jgiven.Stage; + + +import com.tngtech.jgiven.annotation.ExpectedScenarioState; + +import org.jhotdraw.gui.JFontChooser; + + +import java.awt.*; + +public class WhenChoosingFont extends Stage { + @ExpectedScenarioState + JFontChooser fontChooser; + Font font; + + public WhenChoosingFont choosing_font(Font font) { + this.font = font; + fontChooser.setFont(font); + return self(); + } +} diff --git a/pom.xml b/pom.xml index 5f6c7eef5..1063c3941 100644 --- a/pom.xml +++ b/pom.xml @@ -8,11 +8,22 @@ JHotDraw - + github - GitHub external Packages - https://maven.pkg.github.com/sweat-tek/MavenRepository + GitHub Packages + https://maven.pkg.github.com/Benjamand/JHotDraw15 + + + github + GitHub Packages + https://maven.pkg.github.com/Benjamand/JHotDraw15 + + + + + central + https://repo.maven.apache.org/maven2