Skip to content
Advertisement

Java clipboard is not shared with system

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

User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement