Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ public class RichEditorDemoWindow extends Stage {

public RichEditorDemoWindow() {
editor = new RichTextArea();
editor.getInputMap().registerFunction(RichTextArea.Tag.ERROR_FEEDBACK, this::errorFeedback);
toolbar = new RichEditorToolbar();

status = new Label();
Expand Down Expand Up @@ -193,4 +194,9 @@ private String titleString(File f, boolean modified) {
}
return sb.toString();
}

private void errorFeedback() {
// TODO beep
IO.println("Error!");
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -153,6 +153,9 @@ public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
return bimgType;
}
}
if (isOpaque) {
return BufferedImage.TYPE_INT_RGB;
}
switch (fxFormat.getType()) {
default:
case BYTE_BGRA_PRE:
Expand Down Expand Up @@ -198,10 +201,12 @@ public static WritableImage toFXImage(BufferedImage bimg, WritableImage wimg) {
}

private static boolean checkFXImageOpaque(PixelReader pr, int iw, int ih) {
for (int x = 0; x < iw; x++) {
for (int y = 0; y < ih; y++) {
Color color = pr.getColor(x,y);
if (color.getOpacity() != 1.0) {
int[] pixels = new int[iw];
WritablePixelFormat<IntBuffer> format = PixelFormat.getIntArgbPreInstance();
for (int y = 0; y < ih; y++) {
pr.getPixels(0, y, iw, 1, format, pixels, 0, iw);
for (int pixel : pixels) {
if ((pixel >>> 24) != 0xff) {
return false;
}
}
Expand Down Expand Up @@ -251,11 +256,11 @@ public static BufferedImage fromFXImage(Image img, BufferedImage bimg) {
case INT_ARGB:
case BYTE_BGRA_PRE:
case BYTE_BGRA:
// Check fx image opacity only if
// supplied BufferedImage is without alpha channel
if (bimg != null &&
(bimg.getType() == BufferedImage.TYPE_INT_BGR ||
bimg.getType() == BufferedImage.TYPE_INT_RGB)) {
case BYTE_INDEXED:
boolean opacityMatters = bimg == null ||
bimg.getType() == BufferedImage.TYPE_INT_BGR ||
bimg.getType() == BufferedImage.TYPE_INT_RGB;
if (opacityMatters) {
srcPixelsAreOpaque = checkFXImageOpaque(pr, iw, ih);
}
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

package com.sun.jfx.incubator.scene.control.richtext.util;

import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
Expand Down Expand Up @@ -434,6 +435,35 @@ public static String readString(InputStream in) throws IOException {
return sb.toString();
}

private static byte[] writeImage(Image im, String format) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(65536);
try {
// using disk cache slows things down
boolean old = ImageIO.getUseCache();
ImageIO.setUseCache(false);
try {
BufferedImage bi = ImgUtil.fromFXImage(im, null);
ImageIO.write(bi, format, out);
} finally {
ImageIO.setUseCache(old);
}
} finally {
out.close();
}
return out.toByteArray();
}

/**
* Writes an Image to a byte array in JPG format.
*
* @param im source image
* @return byte array containing JPG image
* @throws IOException if an I/O error occurs
*/
public static byte[] writeJPG(Image im) throws IOException {
return writeImage(im, "JPG");
}

/**
* Writes an Image to a byte array in PNG format.
*
Expand All @@ -442,11 +472,7 @@ public static String readString(InputStream in) throws IOException {
* @throws IOException if an I/O error occurs
*/
public static byte[] writePNG(Image im) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream(65536);
// this might conflict with user-set value
ImageIO.setUseCache(false);
ImageIO.write(ImgUtil.fromFXImage(im, null), "PNG", out);
return out.toByteArray();
return writeImage(im, "PNG");
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

package jfx.incubator.scene.control.richtext.model;

import java.io.IOException;
import java.io.OutputStream;
import javafx.scene.image.Image;
import javafx.scene.input.DataFormat;
import com.sun.jfx.incubator.scene.control.richtext.EmbeddedImageHelper;
import com.sun.jfx.incubator.scene.control.richtext.SegmentStyledInput;
import com.sun.jfx.incubator.scene.control.richtext.util.RichUtils;
import jfx.incubator.scene.control.richtext.StyleResolver;
import jfx.incubator.scene.control.richtext.TextPos;

/**
* Facilitates importing of images into the RichTextModel.
* The image is imported via lossless compression (PNG).
*
* @since 28
*/
public class ImageFormatHandler extends DataFormatHandler {

private static final ImageFormatHandler instance = new ImageFormatHandler();

/**
* Constructor.
*/
private ImageFormatHandler() {
super(DataFormat.IMAGE);
}

/**
* Returns the singleton instance of {@code ImageFormatHandler}.
* @return the singleton instance of {@code ImageFormatHandler}
*/
public static final ImageFormatHandler getInstance() {
return instance;
}

/**
* {@inheritDoc}
*
* <p>The type of {@code input} must be {@code Image}.
*/
@Override
public StyledInput createStyledInput(Object input, StyleAttributeMap attr) throws IOException {
Image im = (Image)input;
double w = im.getWidth();
double h = im.getHeight();
byte[] b = RichUtils.writePNG(im);
EmbeddedImage em = EmbeddedImageHelper.create(b, w, h, EmbeddedImage.AUTO, EmbeddedImage.AUTO, true);
StyleAttributeMap a = StyleAttributeMap.of(StyleAttributeMap.EMBEDDED_IMAGE, em);
return new SegmentStyledInput(StyledSegment.of(" ", a));
}

@Override
public Object copy(StyledTextModel m, StyleResolver r, TextPos start, TextPos end) throws IOException {
throw new UnsupportedOperationException();
}

@Override
public void save(StyledTextModel m, StyleResolver r, TextPos start, TextPos end, OutputStream out) throws IOException {
throw new UnsupportedOperationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public RichTextModel() {
registerDataFormatHandler(RtfFormatHandler.getInstance(), true, true, 300);
registerDataFormatHandler(HtmlExportFormatHandler.getInstance(), true, false, 200);
registerDataFormatHandler(FileListFormatHandler.getInstance(), false, true, 100);
registerDataFormatHandler(ImageFormatHandler.getInstance(), false, true, 50);
registerDataFormatHandler(PlainTextFormatHandler.getInstance(), true, true, 0);
// always has at least one paragraph
paragraphs.add(new RParagraph());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -450,10 +450,10 @@ public final DataFormatHandler getDataFormatHandler(DataFormat format, boolean f
*/
public void fireChangeEvent(TextPos start, TextPos end, int charsTop, int linesAdded, int charsBottom) {
ContentChange ch = ContentChange.ofEdit(start, end, charsTop, linesAdded, charsBottom);
markers.update(start, end, charsTop, linesAdded, charsBottom);
for (Listener li : listeners) {
li.onContentChange(ch);
}
markers.update(start, end, charsTop, linesAdded, charsBottom);
}

/**
Expand Down
148 changes: 75 additions & 73 deletions tests/system/src/test/.classpath
Original file line number Diff line number Diff line change
@@ -1,73 +1,75 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry combineaccessrules="false" kind="src" path="/swing">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/base">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.base/com.sun.javafx=ALL-UNNAMED:javafx.base/test.util.memory=ALL-UNNAMED:javafx.base/test.javafx.util=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/graphics">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.graphics/com.sun.glass.ui=ALL-UNNAMED:javafx.graphics/com.sun.glass.ui.monocle=ALL-UNNAMED:javafx.graphics/com.sun.javafx.sg.prism=ALL-UNNAMED:javafx.graphics/com.sun.prism.impl=ALL-UNNAMED:javafx.graphics/com.sun.javafx.image.impl=ALL-UNNAMED:javafx.graphics/com.sun.glass.events=ALL-UNNAMED:javafx.graphics/com.sun.javafx.application=ALL-UNNAMED:javafx.graphics/com.sun.javafx.css=ALL-UNNAMED:javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED:javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED:javafx.graphics/com.sun.glass.ui.mac=ALL-UNNAMED:javafx.graphics/com.sun.glass.ui.win=ALL-UNNAMED:javafx.graphics/com.sun.javafx.scene.text=ALL-UNNAMED:javafx.graphics/com.sun.javafx.text=ALL-UNNAMED:javafx.graphics/com.sun.javafx.font=ALL-UNNAMED:javafx.graphics/com.sun.javafx.menu=ALL-UNNAMED:javafx.graphics/com.sun.javafx.tk.quantum=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/controls">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.controls/test.com.sun.javafx.scene.control.infrastructure=ALL-UNNAMED:javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/jsobject">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/fxml">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/media">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/web">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.web/com.sun.webkit=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="java.desktop/sun.awt.datatransfer=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin" path="java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin" path="resources">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="bin"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry combineaccessrules="false" kind="src" path="/swing">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/base">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.base/com.sun.javafx=ALL-UNNAMED:javafx.base/test.util.memory=ALL-UNNAMED:javafx.base/test.javafx.util=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/graphics">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.graphics/com.sun.glass.ui=ALL-UNNAMED:javafx.graphics/com.sun.glass.ui.monocle=ALL-UNNAMED:javafx.graphics/com.sun.javafx.sg.prism=ALL-UNNAMED:javafx.graphics/com.sun.prism.impl=ALL-UNNAMED:javafx.graphics/com.sun.javafx.image.impl=ALL-UNNAMED:javafx.graphics/com.sun.glass.events=ALL-UNNAMED:javafx.graphics/com.sun.javafx.application=ALL-UNNAMED:javafx.graphics/com.sun.javafx.css=ALL-UNNAMED:javafx.graphics/com.sun.javafx.geom=ALL-UNNAMED:javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED:javafx.graphics/com.sun.glass.ui.mac=ALL-UNNAMED:javafx.graphics/com.sun.glass.ui.win=ALL-UNNAMED:javafx.graphics/com.sun.javafx.scene.text=ALL-UNNAMED:javafx.graphics/com.sun.javafx.text=ALL-UNNAMED:javafx.graphics/com.sun.javafx.font=ALL-UNNAMED:javafx.graphics/com.sun.javafx.menu=ALL-UNNAMED:javafx.graphics/com.sun.javafx.tk.quantum=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/controls">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.controls/test.com.sun.javafx.scene.control.infrastructure=ALL-UNNAMED:javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/jsobject">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/fxml">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/media">
<attributes>
<attribute name="module" value="true"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/web">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="javafx.web/com.sun.webkit=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER">
<attributes>
<attribute name="module" value="true"/>
<attribute name="add-exports" value="java.desktop/sun.awt.datatransfer=ALL-UNNAMED"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/5">
<attributes>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin" path="java">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="bin" path="resources">
<attributes>
<attribute name="gradle_scope" value="test"/>
<attribute name="gradle_used_by_scope" value="test"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" path="/incubator.richtext"/>
<classpathentry kind="src" path="/incubator.input"/>
<classpathentry kind="output" path="bin"/>
</classpath>
1 change: 1 addition & 0 deletions tests/system/src/test/addExports
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
--add-exports javafx.graphics/com.sun.javafx.tk=ALL-UNNAMED
--add-exports=javafx.graphics/com.sun.javafx.tk.quantum=ALL-UNNAMED
--add-exports javafx.graphics/com.sun.prism.impl=ALL-UNNAMED
--add-exports jfx.incubator.richtext/com.sun.jfx.incubator.scene.control.richtext=ALL-UNNAMED
#
--add-exports=javafx.controls/com.sun.javafx.scene.control=ALL-UNNAMED
#
Expand Down
Loading