/***********************************************************************
* mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
***********************************************************************/
package org.mt4j.components.visibleComponents.widgets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.media.opengl.GL;
import javax.media.opengl.glu.GLU;
import org.mt4j.MTApplication;
import org.mt4j.components.TransformSpace;
import org.mt4j.components.clipping.Clip;
import org.mt4j.components.visibleComponents.font.BitmapFont;
import org.mt4j.components.visibleComponents.font.BitmapFontCharacter;
import org.mt4j.components.visibleComponents.font.FontManager;
import org.mt4j.components.visibleComponents.font.IFont;
import org.mt4j.components.visibleComponents.font.IFontCharacter;
import org.mt4j.components.visibleComponents.shapes.MTRectangle;
import org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener;
import org.mt4j.components.visibleComponents.widgets.keyboard.MTKeyboard;
import org.mt4j.input.inputProcessors.componentProcessors.lassoProcessor.IdragClusterable;
import org.mt4j.util.MT4jSettings;
import org.mt4j.util.MTColor;
import org.mt4j.util.math.Matrix;
import org.mt4j.util.math.Tools3D;
import org.mt4j.util.math.Vector3D;
import org.mt4j.util.math.Vertex;
import processing.core.PApplet;
import processing.core.PGraphics;
/**
* The Class MTTextArea. This widget allows to display text with a specified font.
* If the constructor with no fixed text are dimensions is used, the text area will
* expand itself to fit the text in.
* <br>
* If the constructor with fixed dimensions is used, the text will have word wrapping
* and be clipped to the specified dimensions.
*
* @author Christopher Ruff
*/
public class MTTextArea extends MTRectangle implements IdragClusterable, ITextInputListener, Comparable<Object>{
//Standard expand direction is {@link ExpandDirection#UP} for
// backward compatibility.
/**
* Determines the vertical expand direction of the {@link MTTextArea}
* if the text area will expand itself to fit the text in.
*
*
*/
public enum ExpandDirection {
/** Expand the {@link MTTextArea} in top direction if necessary. */
UP,
/** Expand the {@link MTTextArea} in bottom direction if necassary. */
DOWN;
}
/** The pa. */
private PApplet pa;
/** The character list. */
private ArrayList<IFontCharacter> characterList;
/** The font. */
private IFont font;
/** The font b box height. */
private int fontHeight;
/** The show caret. */
private boolean showCaret;
/** The show caret time. */
private long showCaretTime = 1500; //ms
/** The caret time counter. */
private int caretTimeCounter = 0;
/** The enable caret. */
private boolean enableCaret;
/** The caret width. */
private float caretWidth;
private int innerPaddingTop;
private int innerPaddingLeft;
private float totalScrollTextX;
private float totalScrollTextY;
//TODO set font color on the fly
//TODO different font sizes in one textarea?
//TODO (create mode : expand vertically but do word wrap horizontally?)
private static final int MODE_EXPAND = 0;
private static final int MODE_WRAP = 1;
private int mode;
private static ArtificalLineBreak artificialLineBreak;
private ExpandDirection expandDirection ;
/**
* Instantiates a new text area. This constructor creates
* a text area with variable dimensions that expands itself when text is added.
* A default font is used.
*
* @param pApplet the applet
*/
public MTTextArea(PApplet pApplet) {
this(pApplet, FontManager.getInstance().getDefaultFont(pApplet));
}
/**
* Instantiates a new text area. This constructor creates
* a text area with variable dimensions that expands itself when text is added.
*
* @param pApplet the applet
* @param font the font
*/
public MTTextArea(PApplet pApplet, IFont font) {
super( 0, 0, //upper left corner
0, //width
0, //height
pApplet);
init(pApplet, font, MODE_EXPAND);
//Position textarea at 0,0
this.setUpperLeftPos(Vector3D.ZERO_VECTOR);
//Expand vertically at enter
this.setHeightLocal(this.getTotalLinesHeight());
this.setWidthLocal(getMaxLineWidth());
}
/**
* Instantiates a new mT text area.
* This constructor creates a textarea with fixed dimensions.
* If the text exceeds the dimensions the text is clipped.
* A default font is used.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param pApplet the applet
*/
public MTTextArea(float x, float y, float width, float height, PApplet pApplet) {
this(x, y, width, height, FontManager.getInstance().getDefaultFont(pApplet), pApplet);
}
/**
* Instantiates a new mT text area.
* This constructor creates a textarea with fixed dimensions.
* If the text exceeds the dimensions the text is clipped.
*
* @param x the x
* @param y the y
* @param width the width
* @param height the height
* @param font the font
* @param pApplet the applet
*/
public MTTextArea(float x, float y, float width, float height, IFont font, PApplet pApplet) {
super( 0, 0, //upper left corner
width, //width
height, //height
pApplet);
init(pApplet, font, MODE_WRAP);
//Position textarea at x,y
this.setUpperLeftPos(new Vector3D(x,y,0));
}
private void setUpperLeftPos(Vector3D pos){
//Position textarea at 0,0
PositionAnchor prevAnchor = this.getAnchor();
this.setAnchor(PositionAnchor.UPPER_LEFT);
this.setPositionGlobal(pos);
this.setAnchor(prevAnchor);
}
private void init(PApplet pApplet, IFont font, int mode){
this.pa = pApplet;
this.font = font;
this.expandDirection = ExpandDirection.DOWN;
this.mode = mode;
switch (this.mode) {
case MODE_EXPAND:
//We dont have to clip since we expand the area
break;
case MODE_WRAP:
if (MT4jSettings.getInstance().isOpenGlMode()){
//Clip the text to the area
this.setClip(new Clip(pApplet, this.getVerticesLocal()[0].x, this.getVerticesLocal()[0].y, this.getWidthXY(TransformSpace.LOCAL), this.getHeightXY(TransformSpace.LOCAL)));
}
break;
default:
break;
}
characterList = new ArrayList<IFontCharacter>();
if (MT4jSettings.getInstance().isOpenGlMode())
this.setUseDirectGL(true);
fontHeight = font.getFontAbsoluteHeight();
caretWidth = 0;
innerPaddingTop = 5;
innerPaddingLeft = 8;
showCaret = false;
enableCaret = false;
showCaretTime = 1000;
this.setStrokeWeight(1.5f);
this.setStrokeColor(new MTColor(255, 255, 255, 255));
this.setDrawSmooth(true);
//Draw this component and its children above
//everything previously drawn and avoid z-fighting
this.setDepthBufferDisabled(true);
this.totalScrollTextX = 0.0f;
this.totalScrollTextY = 0.0f;
if (artificialLineBreak == null){
artificialLineBreak = new ArtificalLineBreak();
}
this.isBitmapFont = (font instanceof BitmapFont)? true : false;
}
/**
* Sets the font.
* @param font the new font
*/
public void setFont(IFont font){
this.font = font;
this.fontHeight = font.getFontAbsoluteHeight();
this.isBitmapFont = (font instanceof BitmapFont)? true : false;
this.updateLayout();
}
@Override
public void updateComponent(long timeDelta) {
super.updateComponent(timeDelta);
if (enableCaret){
caretTimeCounter+=timeDelta;
if (caretTimeCounter >= showCaretTime && !showCaret){
showCaret = true;
caretTimeCounter = 0;
}else if (caretTimeCounter >= showCaretTime && showCaret){
showCaret = false;
caretTimeCounter = 0;
}
}
}
@Override
public void preDraw(PGraphics graphics) {
super.preDraw(graphics);
//Hack for drawing anti aliased stroke outline over the clipped area
noStrokeSettingSaved = this.isNoStroke();
if (this.mode == MODE_WRAP && this.getClip() != null && !this.isNoStroke()){
this.setNoStroke(true);
}
}
//FIXME TEST Align/round text with screen pixels
private boolean textPositionRounding = true;
private boolean snapVectorDirty = false;
private Vector3D defaultScale = new Vector3D(1,1,1);
private Vector3D globalTranslation = new Vector3D();
private Vector3D rounded = new Vector3D();
private float tolerance = 0.05f;
private boolean isBitmapFont = false;
private Vector3D diff = new Vector3D(0,0,0);
public void setTextPositionRounding(boolean snap){
this.textPositionRounding = snap;
}
public boolean isTextPositionRounding(){
return this.textPositionRounding;
}
@Override
public void setMatricesDirty(boolean baseMatrixDirty) {
super.setMatricesDirty(baseMatrixDirty);
if (baseMatrixDirty)
snapVectorDirty = baseMatrixDirty;
}
private boolean useDisplayList = false;
private boolean contentDisplayListDirty = true;
private void setContentDisplayListDirty(boolean dirty){
this.contentDisplayListDirty = dirty;
this.useDisplayList = (this.contentDisplayListDirty) ? false : true;
}
private int displayListID = 0;
public int useContentDisplayList(){
if (enableCaret)
return -1;
GL gl = GLU.getCurrentGL();
//Delete old one
if (this.displayListID != 0){
gl.glDeleteLists(this.displayListID, 1);
}
//Create new list
int listIDFill = gl.glGenLists(1);
if (listIDFill == 0){
System.err.println("Failed to create fill display list");
}
int thisLineTotalXAdvancement = 0;
int lastXAdvancement = innerPaddingLeft;
//To set caret at most left start pos when charlist empty (looks better)
if (enableCaret && showCaret && characterList.size() == 1){
lastXAdvancement = 0;
}
//Record list
gl.glNewList(listIDFill, GL.GL_COMPILE);
drawCharactersGL(gl, characterList, characterList.size(), lastXAdvancement, thisLineTotalXAdvancement);
gl.glEndList();
if (listIDFill != 0){
useDisplayList = true;
displayListID = listIDFill;
}
this.setContentDisplayListDirty(false);
return (listIDFill == 0)? -1 : listIDFill;
}
@Override
protected void destroyComponent() {
super.destroyComponent();
if (MT4jSettings.getInstance().isOpenGlMode() && this.displayListID != 0){
GL gl = GLU.getCurrentGL();
//Delete old one
if (gl != null){
gl.glDeleteLists(this.displayListID, 1);
}
}
}
@Override
public void drawComponent(PGraphics g) {
super.drawComponent(g);
//FIXME snapping wont be useful if textarea is created at non-integer value!? and if Camera isnt default camera
//if global matrix set dirty and comp not scaled -> calculate new diff vector -> apply
//if snap enabled -> apply diff vector
boolean applySnap = false;
if (isBitmapFont && textPositionRounding){
if (snapVectorDirty){ //Calc new snap vector
Matrix m = this.getGlobalMatrix();
if (m.getScale().equalsVectorWithTolerance(defaultScale, tolerance)){ //Only if no scale applied
applySnap = true;
globalTranslation.setXYZ(m.m03, m.m13, m.m23);
rounded.setXYZ(Math.round(globalTranslation.x), Math.round(globalTranslation.y), Math.round(globalTranslation.z));
// rounded.setXYZ((int)globalTranslation.x, (int)globalTranslation.y, (int)globalTranslation.z);
rounded.subtractLocal(globalTranslation);
diff.setXYZ(rounded.x, rounded.y, rounded.z);
snapVectorDirty = false;
g.pushMatrix();
g.translate(diff.x, diff.y, diff.z);
}else{ //global matrix was set dirty but the textarea is scaled -> dont apply snapvector because it gets blurry anyway if scaled
// snapVectorDirty = false; //because only if scale changes back to 1,1,1 we have to calc new snapvector again
applySnap = false;
}
}else{ //new Snap vector already calculated since global matrix was changed
applySnap = true;
g.pushMatrix();
g.translate(diff.x, diff.y, diff.z);
}
}
//Add caret if its time
if (enableCaret && showCaret){
characterList.add(this.getFont().getFontCharacterByUnicode("|"));
}
int charListSize = characterList.size();
int thisLineTotalXAdvancement = 0;
int lastXAdvancement = innerPaddingLeft;
//Account for TOP inner padding if using WRAP mode -> translate text
switch (this.mode) {
case MODE_EXPAND:
//Dont need to translate for innerpadding TOP because we do that in setHeight() making the whole textarea bigger
g.pushMatrix(); //FIXME TEST
g.translate(0, innerPaddingTop);
break;
case MODE_WRAP:
//Need to translate innerpadding TOP because we shouldnt make the textarea bigger like in expand mode
g.pushMatrix();
g.translate(0, innerPaddingTop);
break;
default:
break;
}
// /*//
//To set caret at most left start pos when charlist empty (looks better)
if (enableCaret && showCaret && charListSize == 1){
lastXAdvancement = 0;
}
// */
if (this.isUseDirectGL()){
GL gl = Tools3D.beginGL(pa);
if (totalScrollTextX != 0.0f && totalScrollTextY != 0.0f){
gl.glTranslatef(totalScrollTextX, totalScrollTextY + font.getFontMaxAscent(), 0);
}else{
gl.glTranslatef(0, font.getFontMaxAscent(), 0);
}
/*
//Disabled so that no new list is created everytime something changes
if (!enableCaret && useDisplayList && this.contentDisplayListDirty){
//Re-Create displaylist
this.useContentDisplayList();
}
*/
//TODO avoid many stateChanges
//in bitmap font mode for example:
//enable textures, enable vertex arrays and color only once!
if(!enableCaret && useDisplayList && this.displayListID != 0){
gl.glCallList(this.displayListID);
}else{
drawCharactersGL(gl, characterList, charListSize, lastXAdvancement, thisLineTotalXAdvancement);
}
Tools3D.endGL(pa);
}
else{ //P3D rendering
g.pushMatrix(); //FIXME TEST text scrolling - but IMHO better done with parent list/scroll container
g.translate(totalScrollTextX, totalScrollTextY + font.getFontMaxAscent(), 0);
for (int i = 0; i < charListSize; i++) {
IFontCharacter character = characterList.get(i);
//Step to the right by the amount of the last characters x advancement
pa.translate(lastXAdvancement, 0, 0); //original
//Save total amount gone to the right in this line
thisLineTotalXAdvancement += lastXAdvancement;
lastXAdvancement = 0;
//Draw the letter
character.drawComponent(g);
//Check if newLine occurs, goto start at new line
if (character.getUnicode().equals("\n")){
pa.translate(-thisLineTotalXAdvancement, fontHeight, 0);
thisLineTotalXAdvancement = 0;
lastXAdvancement = innerPaddingLeft;
}else{
//If caret is showing and we are at index one before caret calc the advancement
if (enableCaret && showCaret && i == charListSize - 2){
if (character.getUnicode().equals("\t")){
lastXAdvancement = character.getHorizontalDist() - character.getHorizontalDist( ) / 20;
}else{
//approximated value, cant get the real one
lastXAdvancement = 2 + character.getHorizontalDist() - (character.getHorizontalDist() / 3);
}
}else{
lastXAdvancement = character.getHorizontalDist();
}
}
}
g.popMatrix();//FIXME TEST text scrolling - but IMHO better done with parent list/scroll container
}
//FIXME TEST //Innerpadding TOP for wrapped textarea -> translates the text content downwards
switch (this.mode) {
case MODE_EXPAND:
g.popMatrix();
break;
case MODE_WRAP:
//Need to translate innerpadding because we shouldnt make the textarea bigger
g.popMatrix();
break;
default:
break;
}
//remove caret
if (enableCaret && showCaret){
characterList.remove(charListSize-1);
}
//FIXME TEST
if (isBitmapFont && textPositionRounding && applySnap){
g.popMatrix();
}
}
private void drawCharactersGL(GL gl, List<IFontCharacter> characterList, int charListSize, int lastXAdv, int lineTotalAdv){
int lastXAdvancement = lastXAdv;
int thisLineTotalXAdvancement = lineTotalAdv;
for (int i = 0; i < charListSize; i++) {
IFontCharacter character = characterList.get(i);
//Step to the right by the amount of the last characters x advancement
gl.glTranslatef(lastXAdvancement, 0, 0);
//Save total amount gone to the right in this line
thisLineTotalXAdvancement += lastXAdvancement;
lastXAdvancement = 0;
//Draw the letter
character.drawComponent(gl);
//Check if newLine occurs, goto start at new line
if (character.getUnicode().equals("\n")){
gl.glTranslatef(-thisLineTotalXAdvancement, fontHeight, 0);
thisLineTotalXAdvancement = 0;
lastXAdvancement = innerPaddingLeft;
}else{
//If caret is showing and we are at index one before caret calc the advancement to include the caret in the text area
if (enableCaret && showCaret && i == charListSize-2){
if (character.getUnicode().equals("\t")){
lastXAdvancement = character.getHorizontalDist() - character.getHorizontalDist() / 20;
}else{
//approximated value, cant get the real one
lastXAdvancement = 2 + character.getHorizontalDist() - (character.getHorizontalDist() / 3);
}
}else{
lastXAdvancement = character.getHorizontalDist();
}
}
}
}
private boolean noStrokeSettingSaved;
@Override
public void postDraw(PGraphics graphics) {
super.postDraw(graphics);
//Hack for drawing anti aliased stroke outline over the clipped area
if (this.mode == MODE_WRAP && this.getClip()!= null && !noStrokeSettingSaved){
this.setNoStroke(noStrokeSettingSaved);
boolean noFillSavedSetting = this.isNoFill();
this.setNoFill(true);
super.drawComponent(graphics);//Draw only stroke line after we ended clipping do preserve anti aliasing - hack
this.setNoFill(noFillSavedSetting);
}
}
//FIXME TEST scrolling (used in MTTextField for example)
protected void scrollTextX(float amount){
this.totalScrollTextX += amount;
}
protected void scrollTextY(float amount){
this.totalScrollTextY += amount;
}
protected float getScrollTextX() {
return this.totalScrollTextX;
}
protected float getScrollTextY() {
return this.totalScrollTextY;
}
//FIXME TEST ?
/**
* Changes the texture filtering for the textarea's bitmap font.
* (if a bitmap font is used).
* If the parameter is "true" this will allow the text being scaled without getting
* too pixelated. If the text isnt going to be scaled ever, it is best to leave or
* set this to "false" for a sharper text.
* <br>NOTE: Only applies if OpenGL is the renderer and the textarea uses a bitmap font.
* <br>NOTE: This affects the whole bitmap font so if it is used elsewhere it is changed
* there, too.
*
* @param scalable the new bitmap font scalable
*/
public void setBitmapFontTextureFiltered(boolean scalable){
if (MT4jSettings.getInstance().isOpenGlMode() && this.getFont() instanceof BitmapFont){
BitmapFont font = (BitmapFont)this.getFont();
IFontCharacter[] characters = font.getCharacters();
for (int i = 0; i < characters.length; i++) {
IFontCharacter fontCharacter = characters[i];
if (fontCharacter instanceof BitmapFontCharacter) {
BitmapFontCharacter bChar = (BitmapFontCharacter) fontCharacter;
bChar.setTextureFiltered(scalable);
}
}
}
}
/**
* Sets the width local.
*
* @param width the new width local
*/
@Override
public void setWidthLocal(float width){
super.setWidthLocal(width);
// Vertex[] v = this.getVerticesLocal();
// MTColor c = this.getFillColor();
// this.setVertices(
// new Vertex[]{
// v[0],
// new Vertex(width, v[1].getY(), v[1].getZ(), c.getR(), c.getG(), c.getB(), c.getAlpha()),
// new Vertex(width, v[2].getY(), v[2].getZ(), c.getR(), c.getG(), c.getB(), c.getAlpha()),
// v[3],
// v[4]});
switch (this.mode) {
case MODE_EXPAND:
break;
case MODE_WRAP:
//if in MODE_WRAP also reset the size of the CLIP SHAPE!
if (MT4jSettings.getInstance().isOpenGlMode() && this.getClip() != null && this.getClip().getClipShape() instanceof MTRectangle){
MTRectangle clipRect = (MTRectangle)this.getClip().getClipShape();
// clipRect.setWidthLocal(this.getWidthXY(TransformSpace.LOCAL));
//Clip the text to the area
// this.setClip(new Clip(pApplet, this.getVerticesLocal()[0].x, this.getVerticesLocal()[0].y, this.getWidthXY(TransformSpace.LOCAL), this.getHeightXY(TransformSpace.LOCAL)));
// clipRect.setVertices(Vertex.getDeepVertexArrayCopy(this.getVerticesLocal()));
clipRect.setVertices(this.getVerticesLocal());
}
this.updateLayout();
break;
default:
break;
}
}
/**
* Sets the height local.
*
* @param height the new height local
*/
@Override
public void setHeightLocal(float height){
Vertex[] v = this.getVerticesLocal();
// this.setVertices(
// new Vertex[]{
// new Vertex(v[2].getX(), upperLeftLocal.y + height, v[2].getZ(), this.getFillRed(), this.getFillGreen(), this.getFillBlue(), this.getFillAlpha()),
// v[0],
// v[1] ,
// new Vertex(v[2].getX(), upperLeftLocal.y + height, v[2].getZ(), this.getFillRed(), this.getFillGreen(), this.getFillBlue(), this.getFillAlpha()),
// new Vertex(v[3].getX(), upperLeftLocal.y + height, v[3].getZ(), this.getFillRed(), this.getFillGreen(), this.getFillBlue(), this.getFillAlpha()),
// v[4]});
switch (this.mode) {
case MODE_EXPAND:
this.setVertices(new Vertex[]{
// new Vertex(v[0].x, - innerPaddingTop, v[0].z, v[0].getTexCoordU(), v[0].getTexCoordV(), v[0].getR(), v[0].getG(), v[0].getB(), v[0].getA()),
// new Vertex(v[1].x, - innerPaddingTop, v[1].z, v[1].getTexCoordU(), v[1].getTexCoordV(), v[1].getR(), v[1].getG(), v[1].getB(), v[1].getA()),
// new Vertex(v[2].x, - innerPaddingTop + height + (2 * innerPaddingTop), v[2].z, v[2].getTexCoordU(), v[2].getTexCoordV(), v[2].getR(), v[2].getG(), v[2].getB(), v[2].getA()),
// new Vertex(v[3].x, - innerPaddingTop + height + (2 * innerPaddingTop), v[3].z, v[3].getTexCoordU(), v[3].getTexCoordV(), v[3].getR(), v[3].getG(), v[3].getB(), v[3].getA()),
// new Vertex(v[4].x, - innerPaddingTop, v[4].z, v[4].getTexCoordU(), v[4].getTexCoordV(), v[4].getR(), v[4].getG(), v[4].getB(), v[4].getA()),
new Vertex(v[0].x, 0, v[0].z, v[0].getTexCoordU(), v[0].getTexCoordV(), v[0].getR(), v[0].getG(), v[0].getB(), v[0].getA()),
new Vertex(v[1].x, 0, v[1].z, v[1].getTexCoordU(), v[1].getTexCoordV(), v[1].getR(), v[1].getG(), v[1].getB(), v[1].getA()),
new Vertex(v[2].x, height + (2 * innerPaddingTop), v[2].z, v[2].getTexCoordU(), v[2].getTexCoordV(), v[2].getR(), v[2].getG(), v[2].getB(), v[2].getA()),
new Vertex(v[3].x, height + (2 * innerPaddingTop), v[3].z, v[3].getTexCoordU(), v[3].getTexCoordV(), v[3].getR(), v[3].getG(), v[3].getB(), v[3].getA()),
new Vertex(v[4].x, 0, v[4].z, v[4].getTexCoordU(), v[4].getTexCoordV(), v[4].getR(), v[4].getG(), v[4].getB(), v[4].getA()),
});
break;
case MODE_WRAP:
super.setHeightLocal(height);
//if in MODE_WRAP also reset the size of the CLIP SHAPE!
if (MT4jSettings.getInstance().isOpenGlMode() && this.getClip() != null && this.getClip().getClipShape() instanceof MTRectangle){
MTRectangle clipRect = (MTRectangle)this.getClip().getClipShape();
// clipRect.setVertices(Vertex.getDeepVertexArrayCopy(this.getVerticesLocal()));
clipRect.setVertices(this.getVerticesLocal());
}
this.updateLayout();
break;
default:
break;
}
}
/**
* Returns the currently active expand direction of the text area.
* (Only has an impact if the wrap mode of the textarea equals MODE_EXPAND!)
*
* @return the active expand direction
*/
public ExpandDirection getExpandDirection() {
return expandDirection;
}
/**
* Sets the expand direction to be used by the text area if a new line is added.
*(Only has an impact if the wrap mode of the textarea equals MODE_EXPAND!)
* @see {@link ExpandDirection}
* @param direction the expand direction to be used
*/
public void setExpandDirection(ExpandDirection direction) {
expandDirection = direction;
this.updateLayout(); //This wont translate the area to its original place if expand mode was UP and is now down..
}
@Override
public void setSizeLocal(float width, float height) {
if (width > 0 && height > 0){
Vertex[] v = this.getVerticesLocal();
switch (this.mode) {
case MODE_EXPAND:
this.setVertices(new Vertex[]{
// new Vertex(v[0].x, - innerPaddingTop, v[0].z, v[0].getTexCoordU(), v[0].getTexCoordV(), v[0].getR(), v[0].getG(), v[0].getB(), v[0].getA()),
// new Vertex(v[0].x+width, - innerPaddingTop, v[1].z, v[1].getTexCoordU(), v[1].getTexCoordV(), v[1].getR(), v[1].getG(), v[1].getB(), v[1].getA()),
// new Vertex(v[0].x+width, - innerPaddingTop + height + (2 * innerPaddingTop), v[2].getTexCoordV(), v[2].getR(), v[2].getG(), v[2].getB(), v[2].getA()),
// new Vertex(v[3].x, - innerPaddingTop + height + (2 * innerPaddingTop), v[3].z, v[3].getTexCoordU(), v[3].getTexCoordV(), v[3].getR(), v[3].getG(), v[3].getB(), v[3].getA()),
// new Vertex(v[4].x, - innerPaddingTop, v[4].z, v[4].getTexCoordU(), v[4].getTexCoordV(), v[4].getR(), v[4].getG(), v[4].getB(), v[4].getA()),
new Vertex(v[0].x, 0, v[0].z, v[0].getTexCoordU(), v[0].getTexCoordV(), v[0].getR(), v[0].getG(), v[0].getB(), v[0].getA()),
new Vertex(v[0].x+width, 0, v[1].z, v[1].getTexCoordU(), v[1].getTexCoordV(), v[1].getR(), v[1].getG(), v[1].getB(), v[1].getA()),
new Vertex(v[0].x+width, height + (2 * innerPaddingTop), v[2].getTexCoordV(), v[2].getR(), v[2].getG(), v[2].getB(), v[2].getA()),
new Vertex(v[3].x, height + (2 * innerPaddingTop), v[3].z, v[3].getTexCoordU(), v[3].getTexCoordV(), v[3].getR(), v[3].getG(), v[3].getB(), v[3].getA()),
new Vertex(v[4].x, 0, v[4].z, v[4].getTexCoordU(), v[4].getTexCoordV(), v[4].getR(), v[4].getG(), v[4].getB(), v[4].getA()),
});
break;
case MODE_WRAP:
super.setSizeLocal(width, height);
//if in MODE_WRAP also reset the size of the CLIP SHAPE!
if (MT4jSettings.getInstance().isOpenGlMode() && this.getClip() != null && this.getClip().getClipShape() instanceof MTRectangle){
MTRectangle clipRect = (MTRectangle)this.getClip().getClipShape();
//clipRect.setVertices(Vertex.getDeepVertexArrayCopy(this.getVerticesLocal()));
clipRect.setVertices(this.getVerticesLocal());
}
this.updateLayout();
break;
default:
break;
}
}
}
/**
* Appends the string to the textarea.
*
* @param string the string
*/
synchronized public void appendText(String string){
for (int i = 0; i < string.length(); i++) {
appendCharByUnicode(string.substring(i, i+1));
}
}
/**
* Sets the provided string as the text of this textarea.
*
* @param string the string
*/
synchronized public void setText(String string){
clear();
for (int i = 0; i < string.length(); i++) {
appendCharByUnicode(string.substring(i, i+1));
}
//FIXME TEST
/*
if (MT4jSettings.getInstance().isOpenGlMode()){
if (getRenderer() instanceof MTApplication) {
MTApplication app = (MTApplication) getRenderer();
if (app.isRenderThreadCurrent()){
this.useContentDisplayList();
}else{
app.invokeLater(new Runnable() {
public void run() {
useContentDisplayList();
}
});
}
}else{
this.useContentDisplayList();
}
}
*/
}
/* (non-Javadoc)
* @see org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener#getText()
*/
public String getText(){
String returnString = "";
for (Iterator<IFontCharacter> iter = this.characterList.iterator(); iter.hasNext();) {
IFontCharacter character = (IFontCharacter) iter.next();
String unicode = character.getUnicode();
if (!character.equals(MTTextArea.artificialLineBreak)){
returnString += unicode;
}
}
return returnString;
}
/**
* Append char by name.
* @param characterName the character name
*/
synchronized public void appendCharByName(String characterName){
//Get the character from the font
IFontCharacter character = font.getFontCharacterByName(characterName);
if (character == null){
System.err.println("Error adding character with name '" + characterName + "' to the textarea. The font couldnt find the character. -> Trying to use 'missing glyph'");
character = font.getFontCharacterByName("missing-glyph");
if (character != null)
addCharacter(character);
}else{
addCharacter(character);
}
}
/* (non-Javadoc)
* @see org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener#appendCharByUnicode(java.lang.String)
*/
synchronized public void appendCharByUnicode(String unicode){
//Get the character from the font
IFontCharacter character = font.getFontCharacterByUnicode(unicode);
if (character == null){
// System.err.println("Error adding character with unicode '" + unicode + "' to the textarea. The font couldnt find the character. ->Trying to use 'missing glyph'");
character = font.getFontCharacterByUnicode("missing-glyph");
if (character != null)
addCharacter(character);
}else{
addCharacter(character);
}
}
/**
* Gets the characters. Also returns articifially added new line characters that were
* added by the MTTextArea
* @return the characters
*/
public IFontCharacter[] getCharacters(){
return this.characterList.toArray(new IFontCharacter[this.characterList.size()]);
}
/**
* Adds the character.
*
* @param character the character
*/
private void addCharacter(IFontCharacter character){
this.characterList.add(character);
this.characterAdded(character);
this.setContentDisplayListDirty(true);
}
/**
* Invoked everytime a character is added.
*
* @param character the character
*/
protected void characterAdded(IFontCharacter character){
switch (this.mode) {
case MODE_EXPAND:
if (character.getUnicode().equals("\n")){
//Expand vertically at enter
this.setHeightLocal(this.getTotalLinesHeight());
//Moves the Textarea up at a enter character instead of down
if (getExpandDirection() == ExpandDirection.UP)
this.translate(new Vector3D(0, -fontHeight, 0));
}else{
//Expand the textbox to the extend of the widest line width
this.setWidthLocal(getMaxLineWidth());
}
break;
case MODE_WRAP:
float localWidth = this.getWidthXY(TransformSpace.LOCAL);
// float maxLineWidth = this.getMaxLineWidth();
float maxLineWidth = this.getLastLineWidth();
if (this.characterList.size() > 0 && maxLineWidth > localWidth ) {
// if (this.characterList.size() > 0 && maxLineWidth > (localWidth - 2 * this.getInnerPaddingLeft())) {
// this.characterList.add(this.characterList.size() -1 , this.font.getFontCharacterByUnicode("\n"));
try {
int lastSpacePos = getLastWhiteSpace();
if (lastSpacePos != -1 ){ //&& !this.characterList.get(characterList.size()-1).getUnicode().equals("\n")
// this.characterList.add(lastSpacePos + 1, this.font.getFontCharacterByUnicode("\n"));
this.characterList.add(lastSpacePos + 1, MTTextArea.artificialLineBreak);
}else{
return;
}
} catch (Exception e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
private int getLastWhiteSpace(){
for (int i = this.characterList.size()-1; i > 0; i--) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals(" ")){
return i;
}else if (character.getUnicode().equals("\n")){// stop search when newline found before first whitespace
return -1;
}
}
return -1;
}
/**
* When Character removed.
*
* @param character the character
*/
protected void characterRemoved(IFontCharacter character){
switch (this.mode) {
case MODE_EXPAND:
//Resize text field
if (character.getUnicode().equals("\n")){
//Reduce field vertically at enter
this.setHeightLocal(this.getTotalLinesHeight());
//makes the textarea go down when a line is removed instead staying at the same loc.
if (getExpandDirection() == ExpandDirection.UP)
translate(new Vector3D(0, fontHeight, 0));
}else{
//Reduce field horizontally
this.setWidthLocal(getMaxLineWidth());
}
break;
case MODE_WRAP:
break;
default:
break;
}
}
/**
* resets the textarea, clears all characters.
*/
public void clear(){
while (!characterList.isEmpty()){
removeLastCharacter();
}
}
/**
* Removes the last character in the textarea.
*/
synchronized public void removeLastCharacter(){
if (this.characterList.isEmpty())
return;
//REMOVE THE CHARACTER
IFontCharacter lastCharacter = this.characterList.get(this.characterList.size()-1);
this.characterList.remove(this.characterList.size()-1);
this.characterRemoved(lastCharacter);
this.setContentDisplayListDirty(true);
}
/**
* Gets the last line width.
*
* @return the last line width
*/
protected float getLastLineWidth(){
float currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;;
}else{
currentLineWidth += character.getHorizontalDist();
}
}
return currentLineWidth;
}
/**
* Gets the max line width. The padding is also added.
*
* @return the max line width
*/
protected float getMaxLineWidth(){
float currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
float maxWidth = currentLineWidth;
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
if (currentLineWidth > maxWidth){
maxWidth = currentLineWidth;
}
currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
}else{
currentLineWidth += character.getHorizontalDist();
if (currentLineWidth > maxWidth){
maxWidth = currentLineWidth;
}
}
}
return maxWidth;
}
/**
* Gets the total lines height. Padding is not included
*
* @return the total lines height
*/
protected float getTotalLinesHeight(){
float height = fontHeight ;//
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
height += fontHeight;
}
}
return height;
}
public void setInnerPadding(int innerPadding){
this.setInnerPaddingTop(innerPadding);
this.setInnerPaddingLeft(innerPadding);
}
public float getInnerPaddingTop() {
return this.innerPaddingTop;
}
public void setInnerPaddingTop(int innerPaddingTop) {
this.innerPaddingTop = innerPaddingTop;
switch (this.mode) {
case MODE_EXPAND:
//At MODE_EXPAND we re-set the text so the size gets re-calculated
//We can safely do this since in EXPAND mode we didnt add any artificial control characters
this.updateLayout();
break;
case MODE_WRAP:
//At MODE_WRAP the padding is done with gl_Translate calls so we dont have to reset the size
//TODO also reset? this.setText(this.getText());?
break;
default:
break;
}
}
public float getInnerPaddingLeft() {
return this.innerPaddingLeft;
}
public void setInnerPaddingLeft(int innerPaddingLeft) {
this.innerPaddingLeft = innerPaddingLeft;
switch (this.mode) {
case MODE_EXPAND:
//At MODE_EXPAND we re-set the text so the size gets re-calculated
//We can safely do this since in EXPAND mode we didnt add any artificial control characters
this.updateLayout();
break;
case MODE_WRAP:
// WE HAVE TO RESET THE ORIGINAL TEXT BECAUSE WE BREAK THE LINE AT DIFFERENT POSITIONS IF THE INNERPADDING IS CHANGED!
this.updateLayout();
break;
default:
break;
}
}
/**
* Updates layout. (just does this.setText(this.getText()))
*/
protected void updateLayout(){
if (this.mode == MODE_EXPAND){
this.setHeightLocal(this.getTotalLinesHeight());
this.setWidthLocal(getMaxLineWidth());
}
this.setText(this.getText());
}
/**
* Gets the line count.
*
* @return the line count
*/
public int getLineCount(){
int count = 0;
for (int i = 0; i < this.characterList.size(); i++) {
IFontCharacter character = this.characterList.get(i);
if (character.getUnicode().equals("\n")){
count++;
}
}
return count;
}
/**
* Gets the font.
*
* @return the font
*/
public IFont getFont() {
return font;
}
/**
* Snap to keyboard.
*
* @param mtKeyboard the mt keyboard
*/
public void snapToKeyboard(MTKeyboard mtKeyboard){
//OLD WAY
// this.translate(new Vector3D(30, -(getFont().getFontAbsoluteHeight() * (getLineCount())) + getFont().getFontMaxDescent() - borderHeight, 0));
mtKeyboard.addChild(this);
this.setPositionRelativeToParent(new Vector3D(40, -this.getHeightXY(TransformSpace.LOCAL)*0.5f));
}
public boolean isSelected() {
// TODO Auto-generated method stub
return false;
}
public void setSelected(boolean selected) {
// TODO Auto-generated method stub
}
/**
* Checks if is enable caret.
*
* @return true, if is enable caret
*/
public boolean isEnableCaret() {
return enableCaret;
}
/**
* Sets the enable caret.
*
* @param enableCaret the new enable caret
*/
public void setEnableCaret(boolean enableCaret) {
if (this.getFont().getFontCharacterByUnicode("|") != null){
this.enableCaret = enableCaret;
if (enableCaret){
this.caretWidth = 10;
}else{
this.caretWidth = 0;
}
if (this.mode == MODE_EXPAND){
this.setWidthLocal(this.getMaxLineWidth());
}
}else{
System.err.println("Cant enable caret for this textfield, the font doesent include the letter '|'");
}
this.setContentDisplayListDirty(true);
}
public int compareTo(Object o) {
if (o instanceof MTTextArea) {
MTTextArea ta = (MTTextArea)o;
return this.getText().compareToIgnoreCase(ta.getText());
} else {
return 0;
}
}
/**
* Artifical line break to be used instead of the regular line break
* to indicate that this linebreak was added by the text area itself for
* layout reasons and doesent really belong to the supplied text.
*
* @author Christopher Ruff
*/
private class ArtificalLineBreak implements IFontCharacter{
public void drawComponent(PGraphics g) {}
public void drawComponent(GL gl) { }
public void destroy() { }
public int getHorizontalDist() {
return 0;
}
public String getUnicode() {
return "\n";
}
}
}