Поведение переноса строк в JTextPane

Недавно я работал над проектом текстового редактора Java, и я хотел бы использовать JTextPane заменить старое JTextArea для того, чтобы реализовать подсветку синтаксиса. Тем не менее, JTextPane не хватает методов в JTextArea (такие как append(), getLineStartOffset() и т.д.) и я хочу переопределить их в моем классе MyTextPane (подкласс JTextPane) но столкнулись с неприятностями.

Мой текущий код (только небольшая отдельная часть):

import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;

public class MyTextPane extends JTextPane
{
    public MyTextPane()
    {
        super();
    }

    public void append(String text)
    {
        try
        {
            Document doc = this.getDocument();
            doc.insertString(doc.getLength(),text,null);
        }
        catch (BadLocationException ex)
        {
            //must succeed
            throw new InternalError(ex.getMessage());
        }
    }

    public void insert(String text, int pos)
    {
        try
        {
            this.getStyledDocument().insertString(pos,text,null);
        }
        catch (BadLocationException ex)
        {
            throw new IllegalArgumentException(ex);
        }
    }

    public void replaceRange(String str, int start, int end)
    {
        try
        {
            Document doc = this.getDocument();
            doc.remove(start,end-start);
            doc.insertString(start,str,null);
        }
        catch (BadLocationException ex)
        {
            throw new IllegalArgumentException(ex);
        }
    }

    public void setLineWrap(boolean isLineWrap)
    {
        /*
         * implements later
         */
    }

    public boolean getLineWrap()
    {
        /*
         * implements later
         */
         return true;
    }

    public void setWrapStyleWord(boolean isWrapStyleWord)
    {
        /*
         * implements later
         */
    }

    public boolean getWrapStyleWord()
    {
        /*
         * implements later
         */
        return true;
    }

    public void setTabSize(int size)
    {
        /*
         * implements later
         */        
    }

    public int getTabSize()
    {
        /*
         * implements later
         */
        return 4;
    }

    public int getLineCount()
    {
        //follow JTextArea implementation
        Element root = this.getDocument().getDefaultRootElement();
        return root.getElementCount();
    }

    public int getLineStartOffset(int line) throws BadLocationException
    {
        //follow JTextArea implementation
        int count = this.getLineCount();
        Document doc = this.getDocument();
        if (line < 0)
        {
            throw new BadLocationException("Negative line", -1);
        }
        if (line >= count)
        {
            throw new BadLocationException("No such line", doc.getLength() + 1);
        }
        return doc.getDefaultRootElement().getElement(line).getStartOffset();
    }

    public int getLineEndOffset(int line) throws BadLocationException
    {
        //follow JTextArea implementation
        int count = this.getLineCount();
        Document doc = this.getDocument();
        if (line < 0)
        {
            throw new BadLocationException("Negative line", -1);
        }
        if (line >= count)
        {
            throw new BadLocationException("No such line", doc.getLength() + 1);
        }
        int end = doc.getDefaultRootElement().getElement(line).getEndOffset();
        return (line==count-1)?(end-1):end;
    }

    public int getLineOfOffset(int off) throws BadLocationException
    {
        //follow JTextArea implementation
        Document doc = this.getDocument();
        if (off < 0)
        {
            throw new BadLocationException("Can't translate offset to line", -1);
        }
        if (off > doc.getLength())
        {
            throw new BadLocationException("Can't translate offset to line", doc.getLength() + 1);
        }
        return doc.getDefaultRootElement().getElementIndex(off);
    }

    public static void main(String[] args)
    {
        final SimpleAttributeSet BOLD_SET = new SimpleAttributeSet();
        StyleConstants.setBold(BOLD_SET, true);
        StyleConstants.setForeground(BOLD_SET, new Color(0,0,125));
        SwingUtilities.invokeLater(new Runnable()
        {            
            @Override
            public void run()
            {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                MyTextPane textPane = new MyTextPane();
                frame.add(new JScrollPane(textPane), BorderLayout.CENTER);
                frame.setSize(200,200);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }
}

Как вы можете видеть, я уже добавил некоторые методы, такие как append(), Однако я не могу придумать какие-либо способы управления политикой переноса строк.

Поведение по умолчанию довольно странно: когда есть одно короткое слово и одно длинное слово,

введите описание изображения здесь

если я последовательно введу символы без пробела,

введите описание изображения здесь

сначала это выглядит как завертывание словами. Однако, когда я продолжаю вводить символы,

введите описание изображения здесь

это не обернуть вообще.

Есть ли какой-нибудь элегантный способ контролировать политику упаковки JTextPane? Другими словами, может ли JTextPane обернуть слова как JTextArea? Я нашел так много дубликатов (как это, это и это), но не смог найти решение. Заранее спасибо.

2 ответа

Решение

Это обсуждение той же проблемы: поведение переноса слов в JTextPane начиная с Java 7. Решение, предложенное пользователем StanislavL (который также, кажется, очень активен в Stack Overflow: StanislavL) для поддержки переноса слов, работает для меня с помощью Java 8. Он использует пользовательский WrapEditorKit в качестве редактора для JTextPaneWrapEditorKit класс в свою очередь использует WrapColumnFactory а также WrapLabelView классы).

Объединение этого с примером NonWrappingTextPane (из книги Core Swing: Advanced Programming by Kim Topley) позволяет отключить перенос строк:

import java.awt.*;
import javax.swing.*;

public class WrapTestApp extends JFrame {
    public static void main(final String[] arguments) {
        new WrapTestApp();
    }

    public WrapTestApp() {
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setBounds(800, 400, 200, 200);
        getContentPane().setLayout(new BorderLayout());
        final CustomTextPane textPane = new CustomTextPane(true);
        final JScrollPane scrollPane = new JScrollPane(textPane);
        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        getContentPane().add(scrollPane, BorderLayout.CENTER);
        textPane.setText("ExampleOfTheWrapLongWordWithoutSpaces");
        setVisible(true);
    }
}

CustomTextPane учебный класс:

import javax.swing.*;
import javax.swing.text.*;

public class CustomTextPane extends JTextPane {
    private boolean lineWrap;

    public CustomTextPane(final boolean lineWrap) {
        this.lineWrap = lineWrap;

        if (lineWrap)
            setEditorKit(new WrapEditorKit());
    }

    @Override
    public boolean getScrollableTracksViewportWidth() {
        if (lineWrap)
            return super.getScrollableTracksViewportWidth();
        else
            return getParent() == null
                  || getUI().getPreferredSize(this).width <= getParent().getSize().width;
    }

    private class WrapEditorKit extends StyledEditorKit {
        private final ViewFactory defaultFactory = new WrapColumnFactory();

        @Override
        public ViewFactory getViewFactory() {
            return defaultFactory;
        }
    }

    private class WrapColumnFactory implements ViewFactory {
        @Override
        public View create(final Element element) {
            final String kind = element.getName();
            if (kind != null) {
                switch (kind) {
                    case AbstractDocument.ContentElementName:
                        return new WrapLabelView(element);
                    case AbstractDocument.ParagraphElementName:
                        return new ParagraphView(element);
                    case AbstractDocument.SectionElementName:
                        return new BoxView(element, View.Y_AXIS);
                    case StyleConstants.ComponentElementName:
                        return new ComponentView(element);
                    case StyleConstants.IconElementName:
                        return new IconView(element);
                }
            }

            // Default to text display.
            return new LabelView(element);
        }
    }

    private class WrapLabelView extends LabelView {
        public WrapLabelView(final Element element) {
            super(element);
        }

        @Override
        public float getMinimumSpan(final 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);
            }
        }
    }
}

Мне потребовалось некоторое время, чтобы найти решение этой проблемы, и, поскольку я не нашел его в stackoverflow , я публикую ссылку на решение .

На случай, если этот сайт когда-нибудь перестанет существовать, скопируйте код, который там находится:

      import javax.swing.*;
import javax.swing.text.*;
import java.awt.*;

public class WrapTestApp extends JFrame {

    public static void main(String[] args) {
        new WrapTestApp();
    }

    public WrapTestApp() {
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setSize(200, 200);
        getContentPane().setLayout(new BorderLayout());
        JTextPane jtp = new JTextPane();
        jtp.setEditorKit(new WrapEditorKit());
        JScrollPane jsp = new JScrollPane(jtp);
        jsp.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
        getContentPane().add(jsp, BorderLayout.CENTER);
        jtp.setText("ExampleOfTheWrapLongWordWithoutSpaces");
        setVisible(true);
    }

    class WrapEditorKit extends StyledEditorKit {

        ViewFactory defaultFactory = new WrapColumnFactory();

        public ViewFactory getViewFactory() {
            return defaultFactory;
        }

    }

    class WrapColumnFactory implements ViewFactory {

        public View create(Element elem) {
            String kind = elem.getName();
            if (kind != null) {
                if (kind.equals(AbstractDocument.ContentElementName)) {
                    return new WrapLabelView(elem);
                } else if (kind.equals(AbstractDocument.ParagraphElementName)) {
                    return new ParagraphView(elem);
                } else if (kind.equals(AbstractDocument.SectionElementName)) {
                    return new BoxView(elem, View.Y_AXIS);
                } else if (kind.equals(StyleConstants.ComponentElementName)) {
                    return new ComponentView(elem);
                } else if (kind.equals(StyleConstants.IconElementName)) {
                    return new IconView(elem);
                }
            }

            // default to text display
            return new LabelView(elem);
        }
    }

    class WrapLabelView extends LabelView {

        public WrapLabelView(Element elem) {
            super(elem);
        }

        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);
            }
        }
    }
}

Все кредиты принадлежат user301607 Л.

Другие вопросы по тегам