I have an JEditorPane field formatted as HTML and when I’m copying it to clipboard the system is losing all the line breaks.
Text inside JEditorPane :
<html> <head> </head> <body> <p> Bonjour,<br><br>Merci de faire les actions ci-dessous:<br><br>1- Sauvegarder le fichier AAAA<br>2- Remplacer le contenu du fichier AAAA par le contenu fichier BBBBB <br>3- Relancer au step planté<br><br>Cordialement,<br> </p> </body> </html>
When copying with Ctrl + C and paste it in Notepad++ the text is:
Bonjour, Merci de faire les actions ci-dessous: 1- Sauvegarder le fichier AAAA 2- Remplacer le contenu du fichier AAAA par le contenu fichier BBBB 3- Relancer au step planté Cordialement,
So, I’ve intercepted the Ctrl+C:
public void keyPressed(KeyEvent e) { //Modify clipboard Ctrl+C if ((e.getKeyCode() == KeyEvent.VK_C) && ((e.getModifiers() & KeyEvent.CTRL_MASK) != 0)) { super.keyPressed(e); genericFunctions.setClipboardText(edSolution.getText());//see function bellow }
And I’ve tried do modify it:
static void setClipboardText(String s) { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); StringSelection stringSelection = new StringSelection(s); clipboard.setContents(stringSelection, stringSelection); }
but the text inside clipboard is not changing.
The strange thing is that if I’m using this function bellow:
static void setClipboardText(String s) { Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); Clipboard selection = Toolkit.getDefaultToolkit().getSystemSelection(); StringSelection stringSelection = new StringSelection(s); StringSelection stringS = new StringSelection(s); clipboard.setContents(stringSelection, stringSelection); selection.setContents(stringS, stringS);//error AWT-EventQueue-0" java.lang.NullPointerException" }
even though I’m getting a NullPointerException, the clipboard is well modified with the new value.
Can someone tell me why the clipboard is only refreshed after the error and not before ? How can I announce the System that the clipboard was changed and should read it ?
JDK8 v1.8.0_251
Advertisement
Answer
I going to surmise that the “default” copy operation only copies the text from the editor as “plain text” – or at least, that is one of the options available to the receiver and it’s taking it as a preference (I don’t have easy access to the Java Source to go digging into the default implementation)
As outlined in Copy JTextArea as “text/html” DataFlavor and Copy jTable row with its grid lines into excel/word documents you can override the default “copy” action and insert your own workflow/formatting.
This is a VERY simple example of the concept. When I copy and paste text using this, I get the complete HTML text.
If you’d prefer not simply get the text with line breaks, you’d have to interpret the content yourself and generate a suitable output String
, you could then use DataFlavor.stringFlavor
as the primary data flavour, but is getting beyond the scope.
import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.EventQueue; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringBufferInputStream; import java.io.StringReader; import java.net.URL; import java.util.ArrayList; import java.util.List; import javax.swing.AbstractAction; import javax.swing.JEditorPane; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; public class Test { public static void main(String[] args) { new Test(); } public Test() { 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 JEditorPane editorPane; public TestPane() { URL url = getClass().getResource("Text.html"); editorPane = new JEditorPane(); editorPane.setContentType("html/text"); try { editorPane.setPage(url); } catch (IOException ex) { ex.printStackTrace(); } editorPane.getActionMap().put("copy", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String text = editorPane.getText(); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(new HtmlSelection(text), null); } }); setLayout(new BorderLayout()); add(new JScrollPane(editorPane)); } @Override public Dimension getPreferredSize() { return new Dimension(200, 200); } } private static class HtmlSelection implements Transferable { private static List<DataFlavor> htmlFlavors = new ArrayList<>(3); static { try { htmlFlavors.add(new DataFlavor("text/html;class=java.lang.String")); htmlFlavors.add(new DataFlavor("text/html;class=java.io.Reader")); htmlFlavors.add(new DataFlavor("text/html;charset=UTF-8;class=java.io.InputStream")); } catch (ClassNotFoundException ex) { ex.printStackTrace(); } } private String html; public HtmlSelection(String html) { this.html = html; } public DataFlavor[] getTransferDataFlavors() { return (DataFlavor[]) htmlFlavors.toArray(new DataFlavor[htmlFlavors.size()]); } public boolean isDataFlavorSupported(DataFlavor flavor) { return htmlFlavors.contains(flavor); } public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException { if (String.class.equals(flavor.getRepresentationClass())) { return html; } else if (Reader.class.equals(flavor.getRepresentationClass())) { return new StringReader(html); } else if (InputStream.class.equals(flavor.getRepresentationClass())) { return new StringBufferInputStream(html); } throw new UnsupportedFlavorException(flavor); } } }
Updated
Okay, so after some digging around, I found the JEditorPane
is using the default functionality from JTextComponent
to copy the contents of the Document
to the clipboard…
public void exportToClipboard(JComponent comp, Clipboard clipboard, int action) throws IllegalStateException { if (comp instanceof JTextComponent) { JTextComponent text = (JTextComponent)comp; int p0 = text.getSelectionStart(); int p1 = text.getSelectionEnd(); if (p0 != p1) { try { Document doc = text.getDocument(); String srcData = doc.getText(p0, p1 - p0); StringSelection contents =new StringSelection(srcData); // this may throw an IllegalStateException, // but it will be caught and handled in the // action that invoked this method clipboard.setContents(contents, null); if (action == TransferHandler.MOVE) { doc.remove(p0, p1 - p0); } } catch (BadLocationException ble) {} } } }
The problem with this is, it’s not taking into account, the styled markup. Joy.
So, after a bit more reading, I came across – Copying to the clipboard in Java
So, I modified the original code to make use of the same concept to this…
editorPane.getActionMap().put("copy", new AbstractAction() { @Override public void actionPerformed(ActionEvent e) { String text = editorPane.getText(); text = formattedPlainText(text); StringSelection selection = new StringSelection(text); Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard(); clipboard.setContents(selection, selection); } protected String formattedPlainText(String text) { final StringBuilder sb = new StringBuilder(); HTMLEditorKit.ParserCallback parserCallback = new HTMLEditorKit.ParserCallback() { public boolean readyForNewline; @Override public void handleText(final char[] data, final int pos) { String s = new String(data); sb.append(s.trim()); readyForNewline = true; } @Override public void handleStartTag(final HTML.Tag t, final MutableAttributeSet a, final int pos) { if (readyForNewline && (t == HTML.Tag.DIV || t == HTML.Tag.BR || t == HTML.Tag.P)) { sb.append("n"); readyForNewline = false; } } @Override public void handleSimpleTag(final HTML.Tag t, final MutableAttributeSet a, final int pos) { handleStartTag(t, a, pos); } }; try { new ParserDelegator().parse(new StringReader(text), parserCallback, false); } catch (IOException ex) { ex.printStackTrace(); } return sb.toString(); } });
And I can now when I copy the text I get this…
Bonjour,
Merci de faire les actions ci-dessous:
1- Sauvegarder le fichier AAAA
2- Remplacer le contenu du fichier AAAA par le contenu fichier BBBBB
3- Relancer au step planté
Cordialement,
Cavet
Both examples will ignore the selection and will copy the whole text – this is a limitation of my time. If required, it would take more effort to extract the styled markup from the StyledDocument
and make determinations about how best to format the text