Создание JEditorPane с html помещает правильно отформатированный текст в буфер обмена

У меня есть этот код, чтобы продемонстрировать проблему:

public static void main(String[] args) {
    JFrame frame = new JFrame();
    frame.getContentPane().add(new JEditorPane("text/html", "Hello cruel world<br>\n<font color=red>Goodbye cruel world</font><br>\n<br>\nHello again<br>\n"));
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.pack();
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
}

Если вы выделите весь текст, который появляется во фрейме после запуска приложения, вы можете скопировать его и вставить в MS Word, Apple Pages или Mail, и текст будет правильно отформатирован. Но если вы вставите его в чистый текстовый редактор, такой как TextEdit, Smultron или окно чата Skype, все вставленное содержимое окажется в одной строке.

Что я могу сделать, чтобы текст, скопированный в буфер обмена, можно было вставить с сохранением новых строк?

Я запускаю свой код в Mac OS X 10.7.


person Steve McLeod    schedule 12.10.2011    source источник
comment
Может быть, TextEdit просто не отображает такие вещи? Что происходит, когда вы вставляете его в другой текстовый редактор?   -  person Nate W.    schedule 12.10.2011
comment
@Shakedown, проблема в других текстовых редакторах, таких как Smultron.   -  person Steve McLeod    schedule 12.10.2011
comment
+1 хороший вопрос, ответь   -  person mKorbel    schedule 17.10.2011


Ответы (3)


Не получив ответов, я закатал рукава и провел много исследований и обучения. Решение состоит в том, чтобы создать собственный TransferHandler для компонента и вручную массировать HTML-текст. Со всем этим было непросто разобраться, что могло объяснить отсутствие ответов, которые я получил.

Вот рабочее решение:

import javax.swing.*;
import javax.swing.text.MutableAttributeSet;
import javax.swing.text.html.HTML;
import javax.swing.text.html.HTMLEditorKit;
import javax.swing.text.html.parser.ParserDelegator;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;

public class ScratchSpace {

    public static void main(String[] args) {
        final JFrame frame = new JFrame();
        final JEditorPane pane = new JEditorPane("text/html", "<html><font color=red>Hello</font><br>\u2663<br>World");
        pane.setTransferHandler(new MyTransferHandler());
        frame.getContentPane().add(pane);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

}

class MyTransferHandler extends TransferHandler {

    protected Transferable createTransferable(JComponent c) {
        final JEditorPane pane = (JEditorPane) c;
        final String htmlText = pane.getText();
        final String plainText = extractText(new StringReader(htmlText));
        return new MyTransferable(plainText, htmlText);
    }

    public String extractText(Reader reader) {
        final ArrayList<String> list = new ArrayList<String>();

        HTMLEditorKit.ParserCallback parserCallback = new HTMLEditorKit.ParserCallback() {
            public void handleText(final char[] data, final int pos) {
                list.add(new String(data));
            }

            public void handleStartTag(HTML.Tag tag, MutableAttributeSet attribute, int pos) {
            }

            public void handleEndTag(HTML.Tag t, final int pos) {
            }

            public void handleSimpleTag(HTML.Tag t, MutableAttributeSet a, final int pos) {
                if (t.equals(HTML.Tag.BR)) {
                    list.add("\n");
                }
            }

            public void handleComment(final char[] data, final int pos) {
            }

            public void handleError(final String errMsg, final int pos) {
            }
        };
        try {
            new ParserDelegator().parse(reader, parserCallback, true);
        } catch (IOException e) {
            e.printStackTrace();
        }
        String result = "";
        for (String s : list) {
            result += s;
        }
        return result;
    }


    @Override
    public void exportToClipboard(JComponent comp, Clipboard clip, int action) throws IllegalStateException {
        if (action == COPY) {
            clip.setContents(this.createTransferable(comp), null);
        }
    }

    @Override
    public int getSourceActions(JComponent c) {
        return COPY;
    }

}

class MyTransferable implements Transferable {

    private static final DataFlavor[] supportedFlavors;

    static {
        try {
            supportedFlavors = new DataFlavor[]{
                    new DataFlavor("text/html;class=java.lang.String"),
                    new DataFlavor("text/plain;class=java.lang.String")
            };
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError(e);
        }
    }

    private final String plainData;
    private final String htmlData;

    public MyTransferable(String plainData, String htmlData) {
        this.plainData = plainData;
        this.htmlData = htmlData;
    }

    public DataFlavor[] getTransferDataFlavors() {
        return supportedFlavors;
    }

    public boolean isDataFlavorSupported(DataFlavor flavor) {
        for (DataFlavor supportedFlavor : supportedFlavors) {
            if (supportedFlavor == flavor) {
                return true;
            }
        }
        return false;
    }

    public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
        if (flavor.equals(supportedFlavors[0])) {
            return htmlData;
        }
        if (flavor.equals(supportedFlavors[1])) {
            return plainData;
        }
        throw new UnsupportedFlavorException(flavor);
    }
}
person Steve McLeod    schedule 16.10.2011

Примечание: это не ответ на вопрос, просто комментарий с кодом к ответу @Thorn, связанный с ограничениями безопасности.

В webstartables с разрешениями по умолчанию (то есть без ;-) вы можете запросить у SecurityManager во время выполнения ClipboardService: появится всплывающее диалоговое окно с просьбой разрешить (или запретить) копирование. При этом вы можете заменить действие копирования по умолчанию в textComponent. В демонстрации SwingX мы поддерживаем вставку кода из исходной области следующим образом:

/**
 * Replaces the editor's default copy action in security restricted
 * environments with one messaging the ClipboardService. Does nothing 
 * if not restricted.
 * 
 * @param editor the editor to replace 
 */
public static void replaceCopyAction(final JEditorPane editor) {
    if (!isRestricted()) return;
    Action safeCopy = new AbstractAction() {

        @Override
        public void actionPerformed(ActionEvent e) {
            try {
                ClipboardService cs = (ClipboardService)ServiceManager.lookup
                    ("javax.jnlp.ClipboardService");
                StringSelection transferable = new StringSelection(editor.getSelectedText());
                cs.setContents(transferable);
            } catch (Exception e1) {
                // do nothing
            }
        }
    };
    editor.getActionMap().put(DefaultEditorKit.copyAction, safeCopy);
}

private static boolean isRestricted() {
    SecurityManager manager = System.getSecurityManager();
    if (manager == null) return false;
    try {
        manager.checkSystemClipboardAccess();
        return false;
    } catch (SecurityException e) {
        // nothing to do - not allowed to access
    }
    return true;
}
person kleopatra    schedule 02.12.2011

Спасибо за кодовый пост! Я работаю над запуском приложения в рамках JNLP, которое позволяет пользователю создавать цитаты MLA, а затем копировать/вставлять их в текстовый процессор. Поэтому форматирование должно быть сохранено.

См. http://proctinator.com/citation/.

Есть более простой способ, но я думаю, что мне понадобится подход, который вы продемонстрировали выше, чтобы заставить мое приложение работать с jnlp.

Приведенный ниже код работает для JEditorPane, работающего в неограниченной среде. Но копирование/вставка недоступна напрямую, когда ваше приложение находится в песочнице (например, в случае апплета или файла JNLP, который не запрашивал полные разрешения).

JEditorPane citEditorPane;
//user fills pane with MLA citations.
citEditorPane.selectAll();
citEditorPane.copy();
citEditorPane.select(0, 0);
person Thorn    schedule 02.12.2011
comment
как скопировать разрешение для webstartable в песочнице, см. мой не-ответ-но-комментарий :-) - person kleopatra; 02.12.2011