4

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.

Community
  • 1
  • 1
Karlovsky120
  • 6,212
  • 8
  • 41
  • 94
  • 3
    *"..and JTextArea is way lighter thatn JtextPane, and I have a bunch of those in my program"* If you had a thousand of either, the app. crashed with `OutOfMemoryError` when using one, but not the other, the difference would ***still*** indicate a problem in the memory used by other aspects of the app. – Andrew Thompson Oct 21 '12 at 19:49
  • I understand what are you trying to say, but I don't get the error. It's just that my app is lighter and less memory intensive with JTextArea. That alone is good enough reason. Also, JTextArea has a nicer default look, while JTextPane has none (Nimbus). But none of that solves my problem. – Karlovsky120 Oct 22 '12 at 12:23

2 Answers2

10

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

Community
  • 1
  • 1
Robin
  • 36,233
  • 5
  • 47
  • 99
  • 2
    @StanislavL it is not perfect. For example the caret becomes too large (it is as large as the modified text height, which is far higher then the actual characters). But since he only wanted to increase the spacing with one pixel, I think it is acceptable – Robin Oct 26 '12 at 10:22
  • +1 Good research (and as far as I know, a unique solution to a long standing problem)! I'll be looking at this closer (just to make sure there aren't any other unseen downfalls). – Nick Rippe Oct 26 '12 at 13:27
  • I think this is the easiest solution, and it will work correctly in the majority of cases. – Alexey Ivanov Oct 28 '12 at 19:01
6

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;
            }
        }
    }
}
StanislavL
  • 56,971
  • 9
  • 68
  • 98
  • 1
    That doesn't work for Nimbus. It messes up the default Nimbus LAF for the JTextArea... – Karlovsky120 Oct 25 '12 at 17:00
  • @Karlovsky120 if you are on jdk7, you can try to subclass SynthTextAreaUI instead (didn't try myself, though, there are some particularities with synth uis which might prevent to install them ..) – kleopatra Oct 26 '12 at 07:44
  • I checked it and this solution does not work with Nimbus LAF. The background color is incorrect, the selected text is black on black. – Alexey Ivanov Oct 28 '12 at 18:43
  • It looks like although Swing is very flexible, it's quite hard to modify some aspects of the UI. The only thing that needs to be changed is `ViewFactory` but it's impossible to change it with Nimbus LAF (and other custom LAFs). – Alexey Ivanov Oct 28 '12 at 19:05
  • @Alexey Ivanov you can easily modify both colors and even implement your own selection painting. – StanislavL Oct 29 '12 at 06:19
  • @StanislavL Yes, I know it. But it turns out you cannot inherit the defaults from Nimbus LAF, and change the view implementation at the same time. – Alexey Ivanov Oct 29 '12 at 06:55
  • 1
    @AlexeyIvanov worksforme(exactly the same look as an untweaked area) - for Nimbus subclassing SynthTextAreaUI, of course – kleopatra Oct 29 '12 at 09:23
  • @AlexeyIvanov _ViewFactory but it's impossible to change it_ only because karlovsky insists on using a simple textComponent - which doesn't support plugging the factory - when in fact he seems to require enhanced customization as supported in textPane ;-) That said: actually, I'm surprised that line spacing isn't considered a basic configuration option, looks like an oversight to me. – kleopatra Oct 29 '12 at 10:11
  • @kleopatra Of course, but Eclipse insists that `SynthTextAreaUI` is not visible. – Alexey Ivanov Oct 29 '12 at 10:13
  • 1
    @kleopatra I agree: line spacing is not something needed for plain text. JTextPane supports line spacing without plugging `ViewFactory` by using [`LineSpacing`](http://docs.oracle.com/javase/6/docs/api/javax/swing/text/StyleConstants.html#LineSpacing) paragraph attribute. But I agree `JTextArea` is lighter than `JTextPane`. On the other hand, premature optimization is evil. Unless you see the application uses lots of memory, and it's really because of `JTextPane`, whereas with `JTextArea` memory usage drops, I wouldn't be wasting time to customize `JTextArea` :) – Alexey Ivanov Oct 29 '12 at 10:21
  • @AlexeyIvanov it's scope is package in jdk6, but got public in jdk7 – kleopatra Oct 29 '12 at 10:22
  • @kleopatra I see. I used jdk6. Anyway there could be other LAFs where such customization will become incompatible. – Alexey Ivanov Oct 29 '12 at 10:24