Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating custom swing component out of existing

So, I have this JTexrtArea which is almost perfect for my needs. The only thing wrong with it is the line spacing. I can't set it. (Why not JTextPane? Because spacing CAN be changed in JTextArea and JTextArea is way lighter thatn JTextPane, and I have a bunch of those in my program).

I have asked this question before, and this is the answer that I got from user StanislavL:


To override JTextArea's line spacing take a look at the PlainView (used to render PLainDocument).

There are following lines in the public void paint(Graphics g, Shape a) method

    drawLine(line, g, x, y);
    y += fontHeight;

So you can adapt the rendering fixing y offset.

In the BasicTextAreaUI method to create view. Replace it with your own implementation of the PlainView

public View create(Element elem) {
Document doc = elem.getDocument();
Object i18nFlag = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
    // build a view that support bidi
    return createI18N(elem);
} else {
    JTextComponent c = getComponent();
    if (c instanceof JTextArea) {
    JTextArea area = (JTextArea) c;
    View v;
    if (area.getLineWrap()) {
        v = new WrappedPlainView(elem, area.getWrapStyleWord());
    } else {
        v = new PlainView(elem);
    }
    return v;
    }
}
return null;
}

I grasp the general idea of what he's telling me to do, but I don't know how to do it. Also, I wouldn't like to override the default JTextArea "property", I'd like to have a choice - to use the default one or to use a custom one.

Only change in JTextArea code would be from

y += fontHeight,

to

y+= (fontHeight +(or -) additionalSpacing).

How do I achieve this? Which classes do I use/copy? Where do I put them? How do I make them usable? How do I get the whole thing working?

If you think this is too specific to be useful, maybe someone could write a general tutorial on how to create a custom swing component based 100% on an existing one. Then someone could easely change some values to better adjust it to it's needs.

like image 768
Karlovsky120 Avatar asked Dec 05 '22 14:12

Karlovsky120


2 Answers

I am simply going to copy-paste my answer from your other question.

I'd like to change the spacing inbetweem the rows of a JTextArea

My first thought was that overriding javax.swing.JTextArea#getRowHeight would be sufficient. The javadoc clearly states

Defines the meaning of the height of a row. This defaults to the height of the font.

So I was hoping that by overriding this method, you would adjust the definition and you would get more spacing between the rows. Bummer, didn't work. A quick search on the usages of that method in the JDK revealed the same. It is mainly used to calculate some sizes, but certainly not used when painting text inside the component.

By looking at the source code of the javax.swing.text.PlainView#paint method, I saw that the FontMetrics are used, and those you can easily override in the JTextArea. So second approach was to extend the JTextArea (bwah, extending Swing components but it is for a proof-of-concept)

  private static class JTextAreaWithExtendedRowHeight extends JTextArea{
    private JTextAreaWithExtendedRowHeight( int rows, int columns ) {
      super( rows, columns );
    }

    @Override
    public FontMetrics getFontMetrics( Font font ) {
      FontMetrics fontMetrics = super.getFontMetrics( font );
      return new FontMetricsWrapper( font, fontMetrics );
    }
  }

The FontMetricsWrapper class basically delegates everything, except the getHeight method. In that method I added 10 to the result of the delegate

@Override
public int getHeight() {
  //use +10 to make the difference obvious
  return delegate.getHeight() + 10;
}

And this results in more row spacing (and a caret which is way too long, but that can probably be adjusted).

A little screenshot to illustrate this (not as nice as some of the other ones, but it shows that this approach might work):

Difference between regular text area and extended one

Small disclaimer: this feels like an ugly hack and might result in unexpected issues. I do hope somebody comes with a better solution.

I personally prefer the solution StanislavL is proposing, but this gives you an alternative

like image 82
Robin Avatar answered Dec 14 '22 23:12

Robin


That's a piece of code. It's not finished. Line spacing between wrapped lines is not implemented. You can get full source of WrappedPlainView or PlainView and add your code there to achieve desired line spacing

import javax.swing.*;
import javax.swing.plaf.basic.BasicTextAreaUI;
import javax.swing.text.*;

public class LineSpacingTextArea {

    public static void main(String[] args) {
        JTextArea ta=new JTextArea();
        JFrame fr=new JFrame("Custom line spacing in JTextArea");
        fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        ta.setText("Line 1\nLine 2\nLong text to show how line spacing works");
        ta.setLineWrap(true);
        ta.setWrapStyleWord(true);
        ta.setUI(new CustomTextAreaUI());
        fr.add(new JScrollPane(ta));
        fr.setSize(100,200);
        fr.setLocationRelativeTo(null);

        fr.setVisible(true);
    }

    static class CustomTextAreaUI extends BasicTextAreaUI {

        public View create(Element elem) {
            Document doc = elem.getDocument();
            Object i18nFlag = doc.getProperty("i18n"/*AbstractDocument.I18NProperty*/);
            if ((i18nFlag != null) && i18nFlag.equals(Boolean.TRUE)) {
                // build a view that support bidi
                return super.create(elem);
            } else {
                JTextComponent c = getComponent();
                if (c instanceof JTextArea) {
                    JTextArea area = (JTextArea) c;
                    View v;
                    if (area.getLineWrap()) {
                        v = new CustomWrappedPlainView(elem, area.getWrapStyleWord());
                    } else {
                        v = new PlainView(elem);
                    }
                    return v;
                }
            }
            return null;
        }
    }

    static class CustomWrappedPlainView extends WrappedPlainView {
        public CustomWrappedPlainView(Element elem, boolean wordWrap) {
            super(elem, wordWrap);
        }
        protected void layoutMajorAxis(int targetSpan, int axis, int[] offsets, int[] spans) {
            super.layoutMajorAxis(targetSpan, axis, offsets, spans);
            int ls=spans[0];
            for (int i=0; i<offsets.length; i++) {
                offsets[i]+=i*ls;
            }
        }
    }
}
like image 26
StanislavL Avatar answered Dec 15 '22 00:12

StanislavL