Textarea with line wrapping – Code

Hello,



people who followed my other topic might have realized that I created a non editable textarea with automatic line wrapping for Nifty GUI.

Something people wanted as an alternative to the listbox.



Features of the textarea:



-Non editable.

-Text is added in a new line.

-When the added text is wider than the maximum width, it adds line breaks.

-You can enable/disable autoscroll, which will scroll the textarea to the bottom if it is enabled and the scrollbar has previously been on the bottom.



Later features might include:



-Adding text with a given color. Useful when you want different text colors in the textarea.

-Appending text to the current line.



On to the code:



XML-Definition:

[xml]

<controlDefinition name=“textarea” controller=".TextareaControl">

<panel id=“outerPanel” childLayout=“center” height="$height" width="$width" backgroundColor="#00000000">

<control name=“scrollPanel” id=“textScrollpanel” horizontal=“false” vertical="$scroll" height=“100%” width=“100%” style="$style" backgroundColor="$areaColor">

<panel id=“textpanel” childLayout=“horizontal” backgroundColor="$areaColor" height=“100%” width=“100%”>

<text id=“areatext” style=“base-font” color="$textColor" width=“94%” />

</panel>

</control>

</panel>

</controlDefinition>

[/xml]



In the first line: controller="
.TextareaControl" you have to substitute the * with the package you put the TextareaControl into.



Interface:

[java]

//Your packagename here



import de.lessvoid.nifty.controls.NiftyControl;



/**

  • Defines methods to manipulate a textarea.

    */

    public interface Textarea extends NiftyControl {



    /**
  • Appends the given text to the textarea. It writes it in a new
  • line. Should the text bee too wide, it automatically wraps it.

    *
  • @param text The text to append to the textarea.

    */

    public void appendLine(String text);



    /**
  • Sets if the textarea should autoscroll, i.e. if the vertical scrollbar
  • should automatically move to the bottom when a new line is added to the
  • textarea and the scrollbar has previously been on the bottom. This is the default behavior.

    *
  • @param autoScroll True if the vertical scrollbar should automatically move
  • to the bottom when a new line is added to the textarea and the scrollbar has previously been on the bottom

    */

    public void setAutoscroll(boolean autoScroll);



    /**
  • Clears the text area. Deletes all text in it.

    */

    public void clearTextarea();

    }

    [/java]



    Control:

    [java]

    //Your packagename here



    import de.lessvoid.nifty.Nifty;

    import de.lessvoid.nifty.controls.AbstractController;

    import de.lessvoid.nifty.controls.ScrollPanel;

    import de.lessvoid.nifty.elements.Element;

    import de.lessvoid.nifty.elements.render.TextRenderer;

    import de.lessvoid.nifty.elements.tools.TextBreak;

    import de.lessvoid.nifty.input.NiftyInputEvent;

    import de.lessvoid.nifty.screen.Screen;

    import de.lessvoid.nifty.spi.render.RenderFont;

    import de.lessvoid.nifty.tools.Color;

    import de.lessvoid.nifty.tools.SizeValue;

    import de.lessvoid.xml.xpp3.Attributes;

    import java.util.ArrayList;

    import java.util.List;

    import java.util.Properties;



    /**
  • The standard controller implementation of the Textarea.
  • An Textarea is a scrollpanel which can hold multiple lines of text.
  • It is not dynamically editable like a textfield. However, lines can be dynamically
  • added through methods, in which case the textarea will expand its scrollarea.
  • It also supports automatic line wrapping.

    /

    public class TextareaControl extends AbstractController implements Textarea {



    /
    * The name of the panel where text is being displayed. /

    private static final String TEXTPANEL_NAME = “textpanel”;

    /
    * The name of the text element rendering the text in the textarea. /

    private static final String TEXT_NAME = “areatext”;

    /
    * The ID of the panel which implements scrolling through the textarea, as defined in the control definition. /

    private static final String SCROLLPANEL_NAME = “textScrollpanel”;

    /
    * The reference to the panel holding text. /

    private Element m_textPanel;

    /
    * The reference to the outer panel. /

    private Element m_outerPanel;

    /
    * The reference of the text element holding the text renderer of the text area. /

    private Element m_text;

    /
    * The reference to the panel which implements scrolling through the textarea. /

    private ScrollPanel m_scrollPanel;

    /
    * The reference to the renderer which renders the text in the textarea. /

    private TextRenderer m_textRenderer;

    /
    * True, if the vertical scrollbar of the textarea should autoscroll. /

    private boolean m_autoScroll;

    /
    * The original height of the text area. /

    private int m_originalHeight;

    /
    * If the textarea supports scrolling or not. If it doesn’t and the text exceeds its size, it will get larger. /

    private boolean m_isScrollable;

    /
    * The control definition attributes as specified in the XML for this control. */

    private Attributes m_controlDefinitionAttributes;



    @Override

    public void bind(Nifty nifty, Screen screen, Element element, Properties parameter, Attributes controlDefinitionAttributes) {

    super.bind(element);

    m_textPanel = element.findElementByName(TEXTPANEL_NAME);

    m_outerPanel = element;

    m_text = element.findElementByName(TEXT_NAME);

    m_scrollPanel = element.findNiftyControl(SCROLLPANEL_NAME, ScrollPanel.class);

    m_textRenderer = m_text.getRenderer(TextRenderer.class);



    m_textRenderer.setXoffsetHack(1);

    m_textRenderer.setLineWrapping(true);



    m_scrollPanel.setStepSizeY(12);

    m_scrollPanel.setPageSizeY(50);



    m_autoScroll = true;



    m_originalHeight = m_scrollPanel.getHeight();



    m_isScrollable = controlDefinitionAttributes.getAsBoolean(“scroll”, true);

    m_controlDefinitionAttributes = controlDefinitionAttributes;

    }



    @Override

    public void onStartScreen() {

    if(m_textRenderer.getOriginalText().isEmpty())

    this.appendLine(m_controlDefinitionAttributes.getWithDefault(“text”, “”));

    }



    @Override

    public boolean inputEvent(NiftyInputEvent inputEvent) {

    return false;

    }



    @Override

    public void appendLine(String text) {

    String[] wrappedText = this.wrapText(text.split(“n”, -1));

    int oldHeight = m_textRenderer.getTextHeight();



    for(String line : wrappedText) {

    String originalText = m_textRenderer.getOriginalText();



    if(!originalText.isEmpty()) {

    m_textRenderer.setText(originalText + “n” + line);

    } else {

    m_textRenderer.setText(line);

    }

    }



    if(m_textPanel.getHeight() < m_textRenderer.getTextHeight()) {

    if(m_isScrollable) {

    m_textPanel.setConstraintHeight(new SizeValue(m_textRenderer.getTextHeight() + “px”));

    m_scrollPanel.getElement().layoutElements();



    if(m_autoScroll && (m_scrollPanel.getVerticalPos() == oldHeight - m_originalHeight || oldHeight - m_originalHeight < 0))

    m_scrollPanel.setVerticalPos(m_textRenderer.getTextHeight());

    } else {

    m_outerPanel.setConstraintHeight(new SizeValue(m_textRenderer.getTextHeight() + “px”));

    m_outerPanel.getParent().layoutElements();

    }

    }



    m_scrollPanel.getElement().layoutElements();

    }



    @Override

    public void setAutoscroll(boolean autoScroll) {

    m_autoScroll = autoScroll;

    }



    @Override

    public void clearTextarea() {

    m_textRenderer.setText("");



    m_textPanel.setConstraintHeight(new SizeValue(m_originalHeight + “px”));

    m_scrollPanel.getElement().layoutElements();



    if(!m_isScrollable) {

    m_outerPanel.setConstraintHeight(new SizeValue(m_originalHeight + “px”));

    m_outerPanel.getParent().layoutElements();

    }

    }



    /**
  • Wraps the given lines of a text into more lines, if they exceed the
  • maximum width of the textarea. Returns the wrapped lines in an array, each
  • element holding one line.

    *
  • @param textLines An array containing lines of a text which should be wrapped.
  • @return An array containing the newly wrapped lines.
  • If wrapping was not necessary on any line, this contains the original textLines.

    */

    private String[] wrapText(final String[] textLines) {

    RenderFont font = m_textRenderer.getFont();



    List < String > lines = new ArrayList < String > ();

    for (String line : textLines) {

    int lineLengthInPixel = font.getWidth(line);

    if (lineLengthInPixel > m_text.getWidth()) {

    lines.addAll(new TextBreak(line, m_text.getWidth(), font).split());

    } else {

    lines.add(line);

    }

    }

    return lines.toArray(new String[0]);

    }

    }

    [/java]



    You can create it from XML with:

    [xml]

    <control name="textarea"/>

    [/xml]



    It supports following attributes: width, height, areaColor, textColor.



    Width and height should be self explanatory. areaColor is the background color of the textarea. textColor the color of the text.



    If you have bugs to report, bugfixes, code optimizations, code additions or features to suggest, please post them here. The code goes with the same license as Nifty GUI.
5 Likes

Looks useful, I’m going to need something similar to this in a few weeks so when I get there I’ll be sure to try this out.

Updated it with a method to clear the textarea. Also made the step and page sizes of the scrollbar a bit higher.

Nice job :).

Thanks for releasing this, I need a scrollable non-editable text box in the application I am building.



I am getting the following error:

[java]WARNING: missing element/control with id [textScrollpanel] for requested control class [de.lessvoid.nifty.controls.ScrollPanel]

Aug 22, 2012 11:58:28 AM com.jme3.app.Application handleError

SEVERE: Uncaught exception thrown in Thread[LWJGL Renderer Thread,5,main]

java.lang.NullPointerException

at org.landcare.scenzgrid.viewer.gui.TextareaControl.bind(TextareaControl.java:60)[/java]



I have included the controlDefinition at the top of the xml file (just after my style definitions). Is this correct?



Thanks!

It seems that I get this problem when using textarea inside of a popup but I don’t get this problem when running in a screen. Any idea of why this might be?



Also, if I set the areaColor to #ffff, the background color is grey not white. I tried changing the color to something other then white which seems to work correctly.



Thanks

Ok, I found the problem. It seems that ScrollPanel control is not found when called from within a Popup. I have added a method called doBind which takes 1 argument (a ScrollPanel). I have also modified bind to prevent a null pointer from being thrown if ScrollPanel isn’t found.



To use TextareaControl in a Popup, make sure you create the popup in the bind method and call showPopup before attempting to get the text area.



Example:

[java]

public void bind(Nifty nifty, Screen screen){

popup = nifty.createPopup(“popup”);

}

public void openPopup(){

nifty.showPopup(nifty.getCurrentScreen(), popup.getId(), null);

ScrollPanel sp = popup.findNiftyControl(“textScrollpanel”, ScrollPanel.class);

TextareaControl control = popup.findNiftyControl(“textarea”, TextareaControl.class);

control.doBind(sp);

control.clearTextarea();

control.appendLine(“Text in a popup textarea”);

}[/java]



Here is the updated code.

[java]



// insert package

import de.lessvoid.nifty.Nifty;

import de.lessvoid.nifty.controls.AbstractController;

import de.lessvoid.nifty.controls.ScrollPanel;

import de.lessvoid.nifty.elements.Element;

import de.lessvoid.nifty.elements.render.TextRenderer;

import de.lessvoid.nifty.elements.tools.TextBreak;

import de.lessvoid.nifty.input.NiftyInputEvent;

import de.lessvoid.nifty.screen.Screen;

import de.lessvoid.nifty.spi.render.RenderFont;

import de.lessvoid.nifty.tools.SizeValue;

import de.lessvoid.xml.xpp3.Attributes;

import java.util.ArrayList;

import java.util.List;

import java.util.Properties;



/**

  • The standard controller implementation of the textarea.
  • A textarea is a scrollpanel which can hold multiple lines of text.
  • It is not dynamically editable like a textfield. However, lines can be dynamically
  • added through methods, in which case the textarea will expand its scrollarea.
  • It also supports automatic line wrapping.

    /

    public class TextareaControl extends AbstractController implements Textarea {



    /
    * The name of the panel where text is being displayed. /

    private static final String TEXTPANEL_NAME = "textpanel";

    /
    * The name of the text element rendering the text in the textarea. /

    private static final String TEXT_NAME = "areatext";

    /
    * The ID of the panel which implements scrolling through the textarea, as defined in the control definition. /

    private static final String SCROLLPANEL_NAME = "textScrollpanel";

    /
    * The reference to the panel holding text. /

    private Element m_textPanel;

    /
    * The reference of the text element holding the text renderer of the text area. /

    private Element m_text;

    /
    * The reference to the panel which implements scrolling through the textarea. /

    private ScrollPanel m_scrollPanel;

    /
    * The reference to the renderer which renders the text in the textarea. /

    private TextRenderer m_textRenderer;

    /
    * True, if the vertical scrollbar of the textarea should autoscroll. /

    private boolean m_autoScroll;

    /
    * The original height of the text area. */

    private int m_originalHeight;



    private Nifty nifty;

    private Screen screen;

    private Element element;

    private Properties parameter;

    private Attributes controlDefinitionAttributes;

    @Override

    public void bind(Nifty nifty, Screen screen, Element element, Properties parameter, Attributes controlDefinitionAttributes) {

    super.bind(element);



    this.nifty = nifty;

    this.screen = screen;

    this.element = element;

    this.parameter = parameter;

    this.controlDefinitionAttributes = controlDefinitionAttributes;



    m_textPanel = element.findElementByName(TEXTPANEL_NAME);

    m_text = element.findElementByName(TEXT_NAME);

    m_scrollPanel = screen.findNiftyControl(SCROLLPANEL_NAME, ScrollPanel.class);

    m_textRenderer = m_text.getRenderer(TextRenderer.class);



    if(m_textRenderer == null) return;

    if(m_scrollPanel == null) return;

    initTextArea();

    }

    public void doBind(ScrollPanel scrollPanel){

    m_textPanel = element.findElementByName(TEXTPANEL_NAME);

    m_text = element.findElementByName(TEXT_NAME);

    m_scrollPanel = scrollPanel;

    m_textRenderer = m_text.getRenderer(TextRenderer.class);

    initTextArea();

    }



    @Override

    public void onStartScreen() {



    }



    @Override

    public boolean inputEvent(NiftyInputEvent inputEvent) {

    return false;

    }



    @Override

    public void appendLine(String text) {

    String[] wrappedText = this.wrapText(text.split("n", -1));

    int oldHeight = m_textRenderer.getTextHeight();



    for(String line : wrappedText) {

    String originalText = m_textRenderer.getOriginalText();



    if(!originalText.isEmpty()) {

    m_textRenderer.setText(originalText + "n" + line);

    } else {

    m_textRenderer.setText(line);

    }

    }



    if(m_textPanel.getHeight() < m_textRenderer.getTextHeight()) {

    m_textPanel.setConstraintHeight(new SizeValue(m_textRenderer.getTextHeight() + "px"));

    m_scrollPanel.getElement().layoutElements();



    if(m_autoScroll && (m_scrollPanel.getVerticalPos() == oldHeight - m_originalHeight || oldHeight - m_originalHeight < 0)) {

    m_scrollPanel.setVerticalPos(m_textRenderer.getTextHeight());

    m_scrollPanel.getElement().layoutElements();

    }

    }

    }



    @Override

    public void setAutoscroll(boolean autoScroll) {

    m_autoScroll = autoScroll;

    }



    @Override

    public void clearTextarea() {

    m_textRenderer.setText("");



    m_textPanel.setConstraintHeight(new SizeValue(m_originalHeight + "px"));

    m_scrollPanel.getElement().layoutElements();

    }



    /**
  • Wraps the given lines of a text into more lines, if they exceed the
  • maximum width of the textarea. Returns the wrapped lines in an array, each
  • element holding one line.

    *
  • @param textLines An array containing lines of a text which should be wrapped.
  • @return An array containing the newly wrapped lines.
  • If wrapping was not necessary on any line, this contains the original textLines.

    */

    private String[] wrapText(final String[] textLines) {

    RenderFont font = m_textRenderer.getFont();



    List < String > lines = new ArrayList < String > ();

    for (String line : textLines) {

    int lineLengthInPixel = font.getWidth(line);

    if (lineLengthInPixel > m_text.getWidth()) {

    lines.addAll(new TextBreak(line, m_text.getWidth(), font).split());

    } else {

    lines.add(line);

    }

    }

    return lines.toArray(new String[0]);

    }

    private void initTextArea(){

    m_textRenderer.setXoffsetHack(1);

    m_textRenderer.setLineWrapping(true);



    m_scrollPanel.setStepSizeY(12);

    m_scrollPanel.setPageSizeY(50);



    m_autoScroll = true;



    m_originalHeight = m_scrollPanel.getHeight();

    }

    }

    [/java]

I updated the textarea. I fixed some bugs and the textarea has now an optional vertical scrollbar.



When the optional vertical scrollbar is disabled, the textarea expands in vertical size instead when its line limit is exceeded and you add text.

Does your updated code handle the popup issues?

No,



if it happens with Popups, then it is most likely an issue with Popups. Void has to look into this.

Can you provide a diff from your previous version and new version? I would like to integrate your new changes with the popup fix. Thanks!

Hello, sorry to revive an old thread, but I was wanting a control builder and am having problems.

public class TextAreaBuilder extends ControlBuilder {
    public TextAreaBuilder(String id) {
        super(id, "textarea");
    }
}

I loaded it like so:
DataHandler.nifty.loadControlFile(/exmpl/client/nifty/text-area-control.xml");

Here is how I use it:
control(new TextAreaBuilder("textarea_chat") { … });

But I am getting the error:
controlDefinition [textarea] missing

Any ideas?
Thanks,
Trevor