I am creating a program that needs to copy an image to clipboard.
The problem is that the image has a transparent background and, whenever I copy it, the image comes out with a black background instead of transparent.
I tried lots of things since 2 days ago but none worked.
The class imageSelection is based on http://www.java2s.com/Tutorial/Java/0120__Development/SettinganimageontheclipboardwithacustomTransferableobjecttoholdtheimage.htm
package Package1;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
/** Transferable image */
public class imageSelection implements Transferable {
private Image image;
/** Creates a "transferable image" */
public imageSelection(Image image) {
this.image = image;
}
public DataFlavor[] getTransferDataFlavors() {
DataFlavor[] transferData = new DataFlavor[] { DataFlavor.imageFlavor }; // <--- Works but gives me a black background instead of transparent
/* I tried this (based on https://stackoverflow.com/questions/15977001/clipboard-copy-from-outlook-always-has-black-background-set-when-retrieved-as-im) but wasn't able to achieve any good result with it.
DataFlavor transferData = null;
try {
transferData = new DataFlavor(Image.class, null); // <---- How to get an object of the type DataFlavor[] from this ( DataFlavor("image/x-emf") is of the type DataFlavor, not DataFlavor[] )
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
error.displayError(e.getStackTrace(), "Error creating DataFlavor (mime type: image/x-emf)");
}
return new DataFlavor[] { transferData }
*/
return transferData;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.imageFlavor.equals(flavor);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (!DataFlavor.imageFlavor.equals(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return image;
}
}
Call:
imageSelection imgSel = new imageSelection(new ImageIcon(emojiLocation).getImage());
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imgSel, null);
Thanks
I am testing the contents by pasting it into Discord (chat app and it does support transparency, I made sure of that).
I am using jdk1.8.0_131.
I am using Windows 10 64bits fully updated.
If needed, full source code here: https://github.com/KingOffNothing/EmojiMenu-for-discord/tree/master/src/Package1
What the program does is change the clipboard to a selected image and then a program written in AHK will simulate the key press ctrl+v that pastes the image.
I was not able to exactly fix the transparency issue but was able to do a workaround (change transparent pixels to match the background's color)
private static BufferedImage fixTransparency(BufferedImage image) {
int width = image.getWidth();
int height = image.getHeight();
Color discordChatColor = new Color(54,57,62, 255);
for (int xx = 0; xx < width; xx++) {
for (int yy = 0; yy < height; yy++) {
Color originalColor = new Color(image.getRGB(xx, yy), true);
if (originalColor.getAlpha() == 0) {
image.setRGB(xx, yy, discordChatColor.getRGB());
}
}
}
return image;
}
Related
Fairly simple, I would like to copy an image into the system clipboard using java and be able to past what is currently in the clipboard into another application, such as word, google docs, etc. etc.
At the moment I've found the common
public static void setClipboard(Image image)
{
ImageTransferable imgSel = new ImageTransferable(image);
Clipboard c = Toolkit.getDefaultToolkit().getSystemClipboard();
c.setContents(imgSel, null);
}
static class ImageTransferable implements Transferable
{
private Image image;
public ImageTransferable (Image image)
{
this.image = image;
}
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException
{
if (isDataFlavorSupported(flavor))
{
return image;
}
else
{
throw new UnsupportedFlavorException(flavor);
}
}
public boolean isDataFlavorSupported (DataFlavor flavor)
{
return flavor == DataFlavor.imageFlavor;
}
public DataFlavor[] getTransferDataFlavors ()
{
return new DataFlavor[] { DataFlavor.imageFlavor };
}
}
Unfortunately this does not put the image into a clipboard in a paste-able state outside of the java program itself. I can get it to successfully pull from the clipboard (c) to paste the image programmatically in the java program itself but not into another window or application.
I did not test the pasted code but i tried to test another one, with some modification it works correctly for me and copy past an image in a given URL to the SystemClipboard :
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.nio.file.Paths;
import javax.imageio.ImageIO;
public class Test {
public static void setClipboard(Image image)
{
ImageSelection imgSel = new ImageSelection(image);
Toolkit.getDefaultToolkit().getSystemClipboard().setContents(imgSel, null);
}
// This class is used to hold an image while on the clipboard.
static class ImageSelection implements Transferable
{
private Image image;
public ImageSelection(Image image)
{
this.image = image;
}
// Returns supported flavors
public DataFlavor[] getTransferDataFlavors()
{
return new DataFlavor[] { DataFlavor.imageFlavor };
}
// Returns true if flavor is supported
public boolean isDataFlavorSupported(DataFlavor flavor)
{
return DataFlavor.imageFlavor.equals(flavor);
}
// Returns image
public Object getTransferData(DataFlavor flavor)
throws UnsupportedFlavorException, IOException
{
if (!DataFlavor.imageFlavor.equals(flavor))
{
throw new UnsupportedFlavorException(flavor);
}
return image;
}
}
public static void main(String[] args) throws IOException {
// put the url of your image
URL url = Paths.get("C:\\Users\\CTW\\Desktop\\oussama _pic.png").toUri().toURL();
// read an image from url
BufferedImage image = ImageIO.read(url);
// resize image to 300x150
Image scaledImage = image.getScaledInstance(300, 150, Image.SCALE_DEFAULT);
// set the image to the system clipboard
setClipboard(scaledImage);
// here open your Paint editor and click ctrl+v and you will see your image pasted there
}
}
Some code copied from here
I want to make a java application that mirrors the screen of a computer (with Windows 10) and shows it in itself. It's nothing too complex (I think): it's just an app that captures everything that is showed on the computer screen and shows it in itself. But I don't want to mirror the main screen. I want to mirror the secondary screen, the projected screen.
---> Reason why I need this:
I project contents on big screens in church which are almost 20 meters away from me and it's a bit hard to see the content, and there's no space to put another monitor next to mine... That's why I need an app that shows me in the main screen what is being exhibited.
I already searched about it on web and didn't find satisfactory results... The maximum I found was screenshot and literally record the screen and save as MP4 or another extension of your preference.
Someone could help me in this, please?
Conceptually, the idea is pretty simple. You want to use an instance of Robot to capture a snapshot of the screen.
The first thing you need to do though, is get the "area" of the screen you want to capture and this is not as easy it as might sound as Java doesn't seem to provide the "screen names" 🤨 (on Windows GraphicsDevice#getIDstring might return the name, but on MacOS it didn't)
So, I started by trying to look at the screen resolutions and tried to figure out which screen I wanted...
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
System.out.println(gd.getDisplayMode());
}
I could have also used getBounds and tried to figure out where each screen was positioned, but what ever works.
Once you have this information, you can use GraphicsDevice#getBounds to get the physical area of the screen, which you can feed to Robot
Now, you need some way to produce and consume those snapshots. You need to be aware of the fact that Swing is not thread safe and is single threaded. This means you can't capture the snapshot from the Event Dispatching Thread and should not try to update the UI from outside of the Event Dispatching Thread. See Concurrency in Swing for more details.
With this in mind, I decided to go with a SwingWorker. It does most of the heavy lifting for us and provides a simplified workflow. See Worker Threads and SwingWorker for more details.
Okay, the next issue to solve, is how to render the screen. The basic concept is just to paint the image onto a component, which isn't that hard, the hard part is scaling the image.
There's been a lot of discussion on the subject, but you could take a look at How do I resize images inside an application when the application window is resized? and Java: maintaining aspect ratio of JPanel background image
The first presents a concept of "scale to fit" and "scale to fill" algorithms and the second presents a better way to scale a image then using Image#getScaledInstance (which the example uses) as getScaledInstance doesn't always present the best result. You could also take a look at Quality of Image after resize very low -- Java which presents some more ideas and discussions.
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.time.Duration;
import java.time.Instant;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingWorker;
class Main {
public static void main(String[] args) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice lstGDs[] = ge.getScreenDevices();
for (GraphicsDevice gd : lstGDs) {
System.out.println(gd.getDisplayMode());
}
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private CaptureWorker worker;
private BufferedImage snapshot;
public TestPane() {
}
#Override
public void addNotify() {
super.addNotify();
startCapture();
}
#Override
public void removeNotify() {
super.removeNotify();
stopCapture();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void startCapture() {
try {
stopCapture();
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gd = ge.getScreenDevices()[0];
worker = new CaptureWorker(gd, new CaptureWorker.Observer() {
#Override
public void imageAvaliable(CaptureWorker source, BufferedImage img) {
TestPane.this.snapshot = img;
repaint();
}
});
worker.execute();
} catch (AWTException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
protected void stopCapture() {
if (worker == null) {
return;
}
worker.stop();
worker = null;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (snapshot != null) {
double scaleFactor = Math.min(1d, getScaleFactorToFill(new Dimension(snapshot.getWidth(), snapshot.getHeight()), getSize()));
int scaleWidth = (int) Math.round(snapshot.getWidth() * scaleFactor);
int scaleHeight = (int) Math.round(snapshot.getHeight() * scaleFactor);
Image imageToRender = snapshot.getScaledInstance(scaleWidth, scaleHeight, Image.SCALE_SMOOTH);
int x = (getWidth() - imageToRender.getWidth(this)) / 2;
int y = (getHeight() - imageToRender.getHeight(this)) / 2;
g2d.drawImage(imageToRender, x, y, this);
}
g2d.dispose();
}
public double getScaleFactor(int iMasterSize, int iTargetSize) {
double dScale = 1;
dScale = (double) iTargetSize / (double) iMasterSize;
return dScale;
}
public double getScaleFactorToFit(Dimension original, Dimension toFit) {
double dScale = 1d;
if (original != null && toFit != null) {
double dScaleWidth = getScaleFactor(original.width, toFit.width);
double dScaleHeight = getScaleFactor(original.height, toFit.height);
dScale = Math.min(dScaleHeight, dScaleWidth);
}
return dScale;
}
public double getScaleFactorToFill(Dimension masterSize, Dimension targetSize) {
double dScaleWidth = getScaleFactor(masterSize.width, targetSize.width);
double dScaleHeight = getScaleFactor(masterSize.height, targetSize.height);
double dScale = Math.max(dScaleHeight, dScaleWidth);
return dScale;
}
}
public class CaptureWorker extends SwingWorker<Void, BufferedImage> {
public interface Observer {
public void imageAvaliable(CaptureWorker source, BufferedImage img);
}
private AtomicBoolean keepRunning = new AtomicBoolean(true);
private Robot bot;
private Rectangle captureBounds;
private final Duration interval = Duration.ofMillis(250);
private Observer observer;
public CaptureWorker(GraphicsDevice device, Observer observer) throws AWTException {
captureBounds = device.getDefaultConfiguration().getBounds();
this.observer = observer;
bot = new Robot();
}
public void stop() {
keepRunning.set(false);
}
#Override
protected void process(List<BufferedImage> chunks) {
BufferedImage img = chunks.get(chunks.size() - 1);
observer.imageAvaliable(this, img);
}
#Override
protected Void doInBackground() throws Exception {
try {
while (keepRunning.get()) {
Instant anchor = Instant.now();
System.out.println("Snapshot");
BufferedImage image = bot.createScreenCapture(captureBounds);
System.out.println("Pubish");
publish(image);
Duration duration = Duration.between(anchor, Instant.now());
System.out.println("Took " + duration.toMillis());
duration = duration.minus(interval);
System.out.println("Time remaining " + duration.toMillis());
if (duration.isNegative()) {
long sleepTime = Math.abs(duration.toMillis());
System.out.println("Sleep for " + sleepTime);
Thread.sleep(sleepTime);
}
}
} catch (Exception exp) {
exp.printStackTrace();
}
return null;
}
}
}
Don't forget
You need to know which screen you want to capture. Take a look at the startCapture method, this where I configured the CaptureWorker. I just grabbed the screen at index 0 and was lucky enough to get the screen I wanted 🤪
Also, this is unsupported. This presents the "how to capture a screen and renderer it a window" portion of your question. How you decide which screen or how you might present that information to the user to configure is all up to you
I want created a program that copies images from a folder into the clipboard, but the images become black.
After doing some research, I found this: Clipboard copy from outlook always has black background set when retrieved as image from Java clipboard object
There he says using image\x-emf fixes the problem. But I can't figure out how to get the TransferData from " new DataFlavor("image/x-emf") "
package Package1;
import java.awt.Image;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import javax.swing.ImageIcon;
/** Transferable image */
public class imageSelection implements Transferable {
private Image image;
/** Creates a transferable object that is an image.
* <p>imageSelection(Image)
* */
public imageSelection(Image image) {
this.image = image;
}
public DataFlavor[] getTransferDataFlavors() {
//DataFlavor[] BlackBackgroundImage = new DataFlavor[] { DataFlavor.imageFlavor }; // <--- Gives me a black background instead of transparent
DataFlavor[] transferData = null;
try {
transferData = new DataFlavor("image/x-emf"); // <---- How to get TransferData from this
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
error.displayError(e.getStackTrace(), "Error creating DataFlavor (mime type: image/x-emf)");
}
return transferData;
}
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.imageFlavor.equals(flavor);
}
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
if (!DataFlavor.imageFlavor.equals(flavor)) {
throw new UnsupportedFlavorException(flavor);
}
return image;
}
}
Method call (where emojiLocation is the path to the image):
imageSelection imgSel = new imageSelection(new ImageIcon(emojiLocation).getImage());
Thanks in advance!
Solution:
return new DataFlavor[] { transferData }
Sorry for missing something so obvious.
This didn't lead me to other problems tho and didn't allow me to achieve a transparent image, so I made a new question in hope to solve this: Set clipboard to transparent image
My issue is, i'm trying to print using Java and it seems to give a random result each time(Look at the pictures and you will understand). The first time I run it the Image prints fine, but the second time there is a black box covering half of the screen. Here is the First Run
and the Second run
Here is the code:
package test;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.ImageObserver;
import javax.swing.*;
import java.awt.print.*;
import java.net.URL;
public class HelloWorldPrinter implements Printable, ActionListener {
private Image ix = null;
public int print(Graphics g, PageFormat pf, int page) throws PrinterException {
if (page > 0) { /* We have only one page, and 'page' is zero-based */
return NO_SUCH_PAGE;
}
/*
* User (0,0) is typically outside the imageable area, so we must
* translate by the X and Y values in the PageFormat to avoid clipping
*/
Graphics2D g2d = (Graphics2D) g;
g2d.translate(pf.getImageableX(), pf.getImageableY());
ix = getImage("Capture.JPG");
g.drawImage(ix, 1, 1, null);
return PAGE_EXISTS;
}
public void actionPerformed(ActionEvent e) {
PrinterJob job = PrinterJob.getPrinterJob();
job.setPrintable(this);
boolean ok = job.printDialog();
if (ok) {
try {
job.print();
} catch (PrinterException ex) {
/* The job did not successfully complete */
}
}
}
public static void main(String args[]) {
UIManager.put("swing.boldMetal", Boolean.FALSE);
JFrame f = new JFrame("Hello World Printer");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
JButton printButton = new JButton("Print Hello World");
printButton.addActionListener(new HelloWorldPrinter());
f.add("Center", printButton);
f.pack();
f.setVisible(true);
}
public Image getImage(String path) {
Image tempImage = null;
try {
URL imageURL = HelloWorldPrinter.class.getResource(path);
imageURL = HelloWorldPrinter.class.getResource(path);
tempImage = Toolkit.getDefaultToolkit().getImage(imageURL);
} catch (Exception e) {
System.out.println(e);
}
return tempImage;
}
}
Thanks for taking the time to read this and I hope you have a solution.
EDIT: I'm using Microsoft Print To PDF so I can view the print. I don't know if it's relevant but I would add it anyways.
MadProgrammer's solution worked.
Don't use Toolkit#getImage, this could using a thread to load the image an or caching the results in unexpected ways, consider using ImageIO.read instead, it will block until the image is fully realised. It's also possible that your getImage method is triggering an exception and is returning a blank image, but since you ignore the exception result, it's hard to know – MadProgrammer
First of all I hope it's not a problem I started a new topic. Tbh I don't have a clue how to ask a question based on an already answered one, so I made this.
I'm pretty new with Java and my problem is the following. I'm writing a little chat program and I'm using a JEditorPane with an HTMLEditorKit to display text in different colors, to display smileys, and display hyperlinks.
My problem is, and after some research I found out the problem might be due to Java7, I can't get the linewrap working properly. I want the text to word wrap and to wrap in the middle of Strings exceeding the width of the component.
The word wrap works fine, but if someone types in a pretty long string the JEditorPane gets expanded and you need to resize the frame to get everything on screen, which is what I do not want to happen.
I've tried a few fixes for this problem, but they only allow letter wrap such that word wrap no longer works. Beside that, I want the user to be able to wrap his text by hitting Enter. For that I'm adding \n to the text and with the fixes this will no longer affect the result and everything's going to be displayed in one line.
I'm feeling like I've spent years in the web to find a solution but unitl now nothing worked for my case, especially since it appeared to be the same fix all the time. I hope you guys can help me.
This means in summary:
What I have:
Line wraps word in case of long strings separated by spaces
if you use Windows and your input contains line wraps created by hitting enter, they will also wrap
If you type in a very long string without spaces, the panel gets expanded and you need to resize the frame
HTML formatting allows me to display different colors as well as hyperlinks and emoticons
What I need:
Word wrap behaviour like it is at the moment in case it is possible but letter wrap ONLY in case of long strings not separated by spaces to prevent the panel from expanding.
Manually added line wraps made by hitting ENTER in the input area or if I copy an pre formatted text into the input panel
HTML formatting like I have already
What I've tried and what didn't help:
jtextpane doesn't wrap text and
JTextPane is not wrapping text
Here is some code to try it yourself. In the bottom left is an input area to type in some text. You can also add line wraps by hitting enter. After clicking on the button you will see the text in the area above.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.border.TitledBorder;
import javax.swing.text.BadLocationException;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.StyleSheet;
#SuppressWarnings("serial")
public class LineWrapTest extends JFrame implements ActionListener, KeyListener {
private JButton btnSend;
private JTextArea textAreaIn;
private JEditorPane textAreaOut;
private HTMLEditorKit kit;
private HTMLDocument doc;
public LineWrapTest() {
this.setSize(600, 500);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setTitle("Linewrap Test");
}
/**
* Not important for problem
*/
public void paintScreen() {
this.setLayout(new BorderLayout());
this.add(this.getPanelOut(), BorderLayout.CENTER);
this.add(this.getPanelIn(), BorderLayout.SOUTH);
this.textAreaIn.requestFocusInWindow();
this.setVisible(true);
}
/**
* Not important for problem
*
* #return panelOut
*/
private JPanel getPanelOut() {
JPanel panelOut = new JPanel();
panelOut.setLayout(new BorderLayout());
this.textAreaOut = new JEditorPane();
this.textAreaOut.setEditable(false);
this.textAreaOut.setContentType("text/html");
this.kit = new HTMLEditorKit();
this.doc = new HTMLDocument();
StyleSheet styleSheet = this.kit.getStyleSheet();
this.kit.setStyleSheet(styleSheet);
this.textAreaOut.setEditorKit(this.kit);
this.textAreaOut.setDocument(this.doc);
TitledBorder border = BorderFactory.createTitledBorder("Output");
border.setTitleJustification(TitledBorder.CENTER);
panelOut.setBorder(border);
panelOut.add(this.textAreaOut);
return panelOut;
}
/**
* Not important for problem
*
* #return panelIn
*/
private JPanel getPanelIn() {
JPanel panelIn = new JPanel();
panelIn.setLayout(new BorderLayout());
this.textAreaIn = new JTextArea();
this.textAreaIn.setLineWrap(true);
this.textAreaIn.setWrapStyleWord(true);
TitledBorder border = BorderFactory.createTitledBorder("Input");
border.setTitleJustification(TitledBorder.CENTER);
panelIn.setBorder(border);
panelIn.add(this.getBtnSend(), BorderLayout.EAST);
panelIn.add(this.textAreaIn, BorderLayout.CENTER);
return panelIn;
}
/**
* Not important for problem
*
* #return btnSend
*/
private JButton getBtnSend() {
this.btnSend = new JButton("Send");
this.btnSend.addActionListener(this);
return this.btnSend;
}
private void append(String text) {
try {
this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null);
} catch (BadLocationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String getHTMLText() {
String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<br/>");
StringBuffer htmlBuilder = new StringBuffer();
htmlBuilder.append("<HTML>");
htmlBuilder.append(txtIn);
htmlBuilder.append("</HTML>");
return htmlBuilder.toString();
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.btnSend) {
this.append(this.getHTMLText());
this.textAreaIn.setText("");
this.textAreaIn.requestFocusInWindow();
}
}
public static void main(String[] args) {
LineWrapTest test = new LineWrapTest();
test.paintScreen();
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER)
if (!this.textAreaIn.getText().trim().isEmpty())
this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR);
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
}
UPDATE:
base on some parts of http://java-sl.com/tip_java7_text_wrapping_bug_fix.html
Somehow I figured it out to step a bit closer to my goal. I've tried to combine the fix for an HTMLEditorKit with an StlyedEditorKit Fix. But I have to be honest, I don't have any clue what I actually did there :( The sad thing is, the manual line wraping does no longer work with this as a replacement of the HTMLEditorKit.
Maybe you can use this as a base for some better implementation.
To use it in my example just create a new class in the project with the CustomEditorKit and replace the HTMLEditorKit in the example with this CustomEditorKit.
You will notice that word and letter wrapping works now, but if you hit ENTER to get your own line wrap this change will no longer appear in the output-panel and everything will be displayed in one line.
Another strange problem of it is, that if you resize the frame the lines will sometimes lay on each other.
import javax.swing.SizeRequirements;
import javax.swing.text.Element;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.ParagraphView;
#SuppressWarnings("serial")
public class CustomEditorKit extends HTMLEditorKit {
#Override
public ViewFactory getViewFactory() {
return new HTMLFactory() {
#Override
public View create(Element e) {
View v = super.create(e);
if (v instanceof InlineView) {
return new InlineView(e) {
#Override
public int getBreakWeight(int axis, float pos, float len) {
return GoodBreakWeight;
}
#Override
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
this.checkPainter();
this.removeUpdate(null, null, null);
}
return super.breakView(axis, p0, pos, len);
}
};
}
else if (v instanceof ParagraphView) {
return new ParagraphView(e) {
#Override
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
if (r == null) {
r = new SizeRequirements();
}
float pref = this.layoutPool.getPreferredSpan(axis);
float min = this.layoutPool.getMinimumSpan(axis);
// Don't include insets, Box.getXXXSpan will include them.
r.minimum = (int) min;
r.preferred = Math.max(r.minimum, (int) pref);
r.maximum = Integer.MAX_VALUE;
r.alignment = 0.5f;
return r;
}
};
}
return v;
}
};
}
}
OK! So, I finally got everything you were having problems with working. It took some research and a lot of trial and error, but here it is:
Here is what I did:
Put the JEditorPane in a JScrollPane so you can scroll up and down as the message gets bigger
Added a custom word wrap. The custom word wrap will wrap words and long words in the desired location of the word. You were right, this is a bug with the current version of Java. http://bugs.sun.com/view_bug.do?bug_id=7125737
Added the ability for the user to wrap to a new line by hitting Enter. This interfered with the custom word wrap though, so you may not like how I achieved this. In the code example I suggest other options.
Preserved your HTMLDocument abilities. I was tempted to not do this, but I found work arounds so that it could be preserved.
The application still uses a JEditorPane, but you could switch it to a JTextPane if you want. I tried both and they were both functional.
So here is the code. It's a bit long and you may wish to change it based on your preferences. I commented where I made changes and tried to explain them.
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.SizeRequirements;
import javax.swing.border.TitledBorder;
import javax.swing.text.*;
import javax.swing.text.html.HTMLDocument;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.InlineView;
import javax.swing.text.html.StyleSheet;
#SuppressWarnings("serial")
public class LineWrapTest extends JFrame implements ActionListener, KeyListener {
//This is the separator.
private String SEPARATOR = System.getProperty("line.separator");
private JButton btnSend;
private JTextArea textAreaIn;
private JEditorPane textAreaOut;
private JScrollPane outputScrollPane;
private HTMLEditorKit kit;
private HTMLDocument doc;
public LineWrapTest() {
this.setSize(600, 500);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setLocationRelativeTo(null);
this.setTitle("Linewrap Test");
}
/**
* Not important for problem
*/
public void paintScreen() {
this.setLayout(new BorderLayout());
this.add(this.getPanelOut(), BorderLayout.CENTER);
this.add(this.getPanelIn(), BorderLayout.SOUTH);
this.textAreaIn.requestFocusInWindow();
this.setVisible(true);
}
/**
* Not important for problem
*
* #return panelOut
*/
private JPanel getPanelOut() {
JPanel panelOut = new JPanel();
panelOut.setLayout(new BorderLayout());
this.textAreaOut = new JEditorPane();
this.textAreaOut.setEditable(false);
this.textAreaOut.setContentType("text/html");
//I added this scroll pane.
this.outputScrollPane = new JScrollPane(this.textAreaOut);
/*
* This is a whole whack of code. It's a combination of two sources.
* It achieves the wrapping you desire: by word and longgg strings
* It is a custom addition to HTMLEditorKit
*/
this.kit = new HTMLEditorKit(){
#Override
public ViewFactory getViewFactory(){
return new HTMLFactory(){
public View create(Element e){
View v = super.create(e);
if(v instanceof InlineView){
return new InlineView(e){
public int getBreakWeight(int axis, float pos, float len) {
//return GoodBreakWeight;
if (axis == View.X_AXIS) {
checkPainter();
int p0 = getStartOffset();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
if (p1 == p0) {
// can't even fit a single character
return View.BadBreakWeight;
}
try {
//if the view contains line break char return forced break
if (getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR) >= 0) {
return View.ForcedBreakWeight;
}
}
catch (BadLocationException ex) {
//should never happen
}
}
return super.getBreakWeight(axis, pos, len);
}
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
checkPainter();
int p1 = getGlyphPainter().getBoundedPosition(this, p0, pos, len);
try {
//if the view contains line break char break the view
int index = getDocument().getText(p0, p1 - p0).indexOf(SEPARATOR);
if (index >= 0) {
GlyphView v = (GlyphView) createFragment(p0, p0 + index + 1);
return v;
}
}
catch (BadLocationException ex) {
//should never happen
}
}
return super.breakView(axis, p0, pos, len);
}
};
}
else if (v instanceof ParagraphView) {
return new ParagraphView(e) {
protected SizeRequirements calculateMinorAxisRequirements(int axis, SizeRequirements r) {
if (r == null) {
r = new SizeRequirements();
}
float pref = layoutPool.getPreferredSpan(axis);
float min = layoutPool.getMinimumSpan(axis);
// Don't include insets, Box.getXXXSpan will include them.
r.minimum = (int)min;
r.preferred = Math.max(r.minimum, (int) pref);
r.maximum = Integer.MAX_VALUE;
r.alignment = 0.5f;
return r;
}
};
}
return v;
}
};
}
};
this.doc = new HTMLDocument();
StyleSheet styleSheet = this.kit.getStyleSheet();
this.kit.setStyleSheet(styleSheet);
this.textAreaOut.setEditorKit(this.kit);
this.textAreaOut.setDocument(this.doc);
TitledBorder border = BorderFactory.createTitledBorder("Output");
border.setTitleJustification(TitledBorder.CENTER);
panelOut.setBorder(border);
//I changed this to add the scrollpane, which now contains
//the JEditorPane
panelOut.add(this.outputScrollPane);
return panelOut;
}
/**
* Not important for problem
*
* #return panelIn
*/
private JPanel getPanelIn() {
JPanel panelIn = new JPanel();
panelIn.setLayout(new BorderLayout());
this.textAreaIn = new JTextArea();
this.textAreaIn.setLineWrap(true);
this.textAreaIn.setWrapStyleWord(true);
//This disables enter from going to a new line. Your key listener does that.
this.textAreaIn.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "none");
//For the key listener to work, it needs to be added to the component
this.textAreaIn.addKeyListener(this);
TitledBorder border = BorderFactory.createTitledBorder("Input");
border.setTitleJustification(TitledBorder.CENTER);
panelIn.setBorder(border);
panelIn.add(this.getBtnSend(), BorderLayout.EAST);
panelIn.add(this.textAreaIn, BorderLayout.CENTER);
return panelIn;
}
/**
* Not important for problem
*
* #return btnSend
*/
private JButton getBtnSend() {
this.btnSend = new JButton("Send");
this.btnSend.addActionListener(this);
return this.btnSend;
}
private void append(String text) {
try {
this.kit.insertHTML(this.doc, this.doc.getLength(), text, 0, 0, null);
} catch (BadLocationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private String getHTMLText() {
//I tried to find a work around for this but I couldn't. It could be done
//by manipulating the HTMLDocument but it's beyond me. Notice I changed
//<br/> to <p/>. For some reason, <br/> no longer went to the next line
//when I added the custom wrap. <p/> seems to work though.
String txtIn = this.textAreaIn.getText().trim().replaceAll(SEPARATOR, "<p/>");
//My IDE recommends you use StringBuilder instead, that's up to you.
//I am not sure what the difference would be.
StringBuffer htmlBuilder = new StringBuffer();
htmlBuilder.append("<HTML>");
htmlBuilder.append(txtIn);
htmlBuilder.append("</HTML>");
return htmlBuilder.toString();
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.btnSend) {
this.append(this.getHTMLText());
this.textAreaIn.setText("");
this.textAreaIn.requestFocusInWindow();
}
}
public static void main(String[] args) {
LineWrapTest test = new LineWrapTest();
test.paintScreen();
}
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER){
if (!this.textAreaIn.getText().trim().isEmpty()) {
//I made this work by defining the SEPARATOR.
//You could use append(Separator) instead if you want.
this.textAreaIn.setText(this.textAreaIn.getText() + SEPARATOR);
}
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
}
Here are (most of) the links that I used to solve this problem:
Enabling word wrap in a JTextPane with HTMLDocument
Custom wrap is a combination of these two:
http://java-sl.com/tip_html_letter_wrap.html
http://java-sl.com/wrap.html
Deleting the keybind for JTextArea:
http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html
If you have any questions whatsoever, just comment below. I will answer them. I sincerely hope this solves your problems
A deadly better solution I found :
The <br> is correctly handled by the HTMLEditorKit, but the Patrick Sebastien's post mentionned that it won't. It's because its ViewFactory threat all InlineView object as wrappable, but the BRView is also an InlineView. See my solution below:
class WrapColumnFactory extends HTMLEditorKit.HTMLFactory {
#Override
public View create(Element elem) {
View v = super.create(elem);
if (v instanceof LabelView) {
// the javax.swing.text.html.BRView (representing <br> tag) is a LabelView but must not be handled
// by a WrapLabelView. As BRView is private, check the html tag from elem attribute
Object o = elem.getAttributes().getAttribute(StyleConstants.NameAttribute);
if ((o instanceof HTML.Tag) && o == HTML.Tag.BR) {
return v;
}
return new WrapLabelView(elem);
}
return v;
}
}
class WrapLabelView extends LabelView {
public WrapLabelView(Element elem) {
super(elem);
}
#Override
public float getMinimumSpan(int axis) {
switch (axis) {
case View.X_AXIS:
return 0;
case View.Y_AXIS:
return super.getMinimumSpan(axis);
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
}