/src/org/mt4j/components/visibleComponents/widgets/MTTextArea.java

http://mt4j.googlecode.com/ · Java · 1331 lines · 767 code · 200 blank · 364 comment · 156 complexity · 64c6300a39465ecffdb3a77ad47b7899 MD5 · raw file

  1. /***********************************************************************
  2. * mt4j Copyright (c) 2008 - 2009, C.Ruff, Fraunhofer-Gesellschaft All rights reserved.
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 3 of the License, or
  7. * (at your option) any later version.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. *
  17. ***********************************************************************/
  18. package org.mt4j.components.visibleComponents.widgets;
  19. import java.util.ArrayList;
  20. import java.util.List;
  21. import javax.media.opengl.GL;
  22. import javax.media.opengl.glu.GLU;
  23. import org.mt4j.MTApplication;
  24. import org.mt4j.components.TransformSpace;
  25. import org.mt4j.components.clipping.Clip;
  26. import org.mt4j.components.css.style.CSSFont;
  27. import org.mt4j.components.css.style.CSSStyle;
  28. import org.mt4j.components.visibleComponents.font.BitmapFont;
  29. import org.mt4j.components.visibleComponents.font.BitmapFontCharacter;
  30. import org.mt4j.components.visibleComponents.font.FontManager;
  31. import org.mt4j.components.visibleComponents.font.IFont;
  32. import org.mt4j.components.visibleComponents.font.IFontCharacter;
  33. import org.mt4j.components.visibleComponents.shapes.MTRectangle;
  34. import org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener;
  35. import org.mt4j.components.visibleComponents.widgets.keyboard.MTKeyboard;
  36. import org.mt4j.input.inputProcessors.componentProcessors.lassoProcessor.IdragClusterable;
  37. import org.mt4j.util.MT4jSettings;
  38. import org.mt4j.util.MTColor;
  39. import org.mt4j.util.math.Matrix;
  40. import org.mt4j.util.math.Tools3D;
  41. import org.mt4j.util.math.Vector3D;
  42. import org.mt4j.util.math.Vertex;
  43. import processing.core.PApplet;
  44. import processing.core.PGraphics;
  45. /**
  46. * The Class MTTextArea. This widget allows to display text with a specified font.
  47. * If the constructor with no fixed text are dimensions is used, the text area will
  48. * expand itself to fit the text in.
  49. * <br>
  50. * If the constructor with fixed dimensions is used, the text will have word wrapping
  51. * and be clipped to the specified dimensions.
  52. *
  53. * @author Christopher Ruff
  54. */
  55. public class MTTextArea extends MTRectangle implements IdragClusterable, ITextInputListener, Comparable<Object>{
  56. //Standard expand direction is {@link ExpandDirection#UP} for
  57. // backward compatibility.
  58. /**
  59. * Determines the vertical expand direction of the {@link MTTextArea}
  60. * if the text area will expand itself to fit the text in.
  61. *
  62. *
  63. */
  64. public enum ExpandDirection {
  65. /** Expand the {@link MTTextArea} in top direction if necessary. */
  66. UP,
  67. /** Expand the {@link MTTextArea} in bottom direction if necassary. */
  68. DOWN
  69. }
  70. /** The pa. */
  71. private PApplet pa;
  72. /** The character list. */
  73. private ArrayList<IFontCharacter> characterList;
  74. /** The font. */
  75. private IFont font;
  76. /** The font b box height. */
  77. private int fontHeight;
  78. /** The show caret. */
  79. private boolean showCaret;
  80. /** The show caret time. */
  81. private long showCaretTime; //ms
  82. /** The caret time counter. */
  83. private int caretTimeCounter = 0;
  84. /** The enable caret. */
  85. private boolean enableCaret;
  86. /** The caret width. */
  87. private float caretWidth;
  88. private int innerPaddingTop;
  89. private int innerPaddingLeft;
  90. private float totalScrollTextX;
  91. private float totalScrollTextY;
  92. private static final int MODE_EXPAND = 0;
  93. private static final int MODE_WRAP = 1;
  94. private int mode;
  95. private static ArtificalLineBreak artificialLineBreak;
  96. private ExpandDirection expandDirection ;
  97. //TODO different font sizes in one textarea?
  98. //TODO (create mode : expand vertically but do word wrap horizontally?
  99. /**
  100. * Instantiates a new text area. This constructor creates
  101. * a text area with variable dimensions that expands itself when text is added.
  102. * A default font is used.
  103. *
  104. * @param pApplet the applet
  105. */
  106. public MTTextArea(PApplet pApplet) {
  107. this(pApplet, FontManager.getInstance().getDefaultFont(pApplet));
  108. }
  109. private boolean ignoreCSSFont = false;
  110. /**
  111. * Instantiates a new text area. This constructor creates
  112. * a text area with variable dimensions that expands itself when text is added.
  113. *
  114. * @param pApplet the applet
  115. * @param font the font
  116. */
  117. public MTTextArea(MTApplication pApplet, CSSFont font) {
  118. this(pApplet, FontManager.getInstance().getDefaultFont(pApplet));
  119. this.getCssHelper().getPrivateStyleSheets().add(new CSSStyle(font,pApplet));
  120. }
  121. /**
  122. * Instantiates a new text area. This constructor creates
  123. * a text area with variable dimensions that expands itself when text is added.
  124. *
  125. * @param pApplet the applet
  126. * @param font the font
  127. */
  128. public MTTextArea(PApplet pApplet, IFont font) {
  129. super( pApplet, 0, //upper left corner
  130. 0, //width
  131. 0, //height
  132. 0);
  133. init(pApplet, font, MODE_EXPAND);
  134. //Position textarea at 0,0
  135. this.setUpperLeftPos(Vector3D.ZERO_VECTOR);
  136. //Expand vertically at enter
  137. this.setHeightLocal(this.getTotalLinesHeight());
  138. this.setWidthLocal(getMaxLineWidth());
  139. //Disable font being overwritten by CSS
  140. this.ignoreCSSFont = true;
  141. }
  142. /**
  143. * Instantiates a new mT text area.
  144. *
  145. * @param x the x
  146. * @param y the y
  147. * @param width the width
  148. * @param height the height
  149. * @param pApplet the applet
  150. * @deprecated constructor will be deleted! Please , use the constructor with the PApplet instance as the first parameter.
  151. */
  152. public MTTextArea(float x, float y, float width, float height, PApplet pApplet) {
  153. this(pApplet, x, y, width, height, FontManager.getInstance().getDefaultFont(pApplet));
  154. }
  155. /**
  156. * Instantiates a new mT text area.
  157. * This constructor creates a textarea with fixed dimensions.
  158. * If the text exceeds the dimensions the text is clipped.
  159. * A default font is used.
  160. * @param pApplet the applet
  161. * @param x the x
  162. * @param y the y
  163. * @param width the width
  164. * @param height the height
  165. */
  166. public MTTextArea(PApplet pApplet, float x, float y, float width, float height) {
  167. this(pApplet, x, y, width, height, FontManager.getInstance().getDefaultFont(pApplet));
  168. }
  169. /**
  170. * Instantiates a new mT text area.
  171. *
  172. * @param x the x
  173. * @param y the y
  174. * @param width the width
  175. * @param height the height
  176. * @param font the font
  177. * @param pApplet the applet
  178. * @deprecated constructor will be deleted! Please , use the constructor with the PApplet instance as the first parameter.
  179. */
  180. public MTTextArea(float x, float y, float width, float height, IFont font, PApplet pApplet) {
  181. this(pApplet, x, y, width, height, font);
  182. }
  183. /**
  184. * Instantiates a new mT text area.
  185. * This constructor creates a textarea with fixed dimensions.
  186. * If the text exceeds the dimensions the text is clipped.
  187. * @param pApplet the applet
  188. * @param x the x
  189. * @param y the y
  190. * @param width the width
  191. * @param height the height
  192. * @param font the font
  193. */
  194. public MTTextArea(PApplet pApplet, float x, float y, float width, float height, IFont font) {
  195. super( pApplet, 0, //upper left corner
  196. 0, //width
  197. width, //height
  198. height);
  199. init(pApplet, font, MODE_WRAP);
  200. //Position textarea at x,y
  201. this.setUpperLeftPos(new Vector3D(x,y,0));
  202. this.setUpperLeftPos(new Vector3D(x,y,0));
  203. //Disable font being overwritten by CSS
  204. this.ignoreCSSFont = true;
  205. }
  206. public boolean isIgnoreCSSFont() {
  207. return ignoreCSSFont;
  208. }
  209. public void setIgnoreCSSFont(boolean ignoreCSSFont) {
  210. this.ignoreCSSFont = ignoreCSSFont;
  211. }
  212. private void setUpperLeftPos(Vector3D pos){
  213. //Position textarea at 0,0
  214. PositionAnchor prevAnchor = this.getAnchor();
  215. this.setAnchor(PositionAnchor.UPPER_LEFT);
  216. this.setPositionGlobal(pos);
  217. this.setAnchor(prevAnchor);
  218. }
  219. public MTTextArea(MTApplication app) {
  220. this(app, app.getCssStyleManager().getDefaultFont(app));
  221. }
  222. private void init(PApplet pApplet, IFont font, int mode){
  223. this.pa = pApplet;
  224. this.font = font;
  225. this.expandDirection = ExpandDirection.DOWN;
  226. this.mode = mode;
  227. switch (this.mode) {
  228. case MODE_EXPAND:
  229. //We dont have to clip since we expand the area
  230. break;
  231. case MODE_WRAP:
  232. if (MT4jSettings.getInstance().isOpenGlMode()){
  233. //Clip the text to the area
  234. this.setClip(new Clip(pApplet, this.getVerticesLocal()[0].x, this.getVerticesLocal()[0].y, this.getWidthXY(TransformSpace.LOCAL), this.getHeightXY(TransformSpace.LOCAL)));
  235. }
  236. break;
  237. default:
  238. break;
  239. }
  240. characterList = new ArrayList<IFontCharacter>();
  241. if (MT4jSettings.getInstance().isOpenGlMode())
  242. this.setUseDirectGL(true);
  243. fontHeight = font.getFontAbsoluteHeight();
  244. caretWidth = 0;
  245. innerPaddingTop = 5;
  246. innerPaddingLeft = 8;
  247. showCaret = false;
  248. enableCaret = false;
  249. showCaretTime = 600;
  250. this.setStrokeWeight(1.5f);
  251. this.setStrokeColor(new MTColor(255, 255, 255, 255));
  252. this.setDrawSmooth(true);
  253. //Draw this component and its children above
  254. //everything previously drawn and avoid z-fighting
  255. this.setDepthBufferDisabled(true);
  256. this.totalScrollTextX = 0.0f;
  257. this.totalScrollTextY = 0.0f;
  258. if (artificialLineBreak == null){
  259. artificialLineBreak = new ArtificalLineBreak();
  260. }
  261. this.isBitmapFont = (font instanceof BitmapFont);
  262. }
  263. /**
  264. * Sets the font.
  265. * @param font the new font
  266. */
  267. public void setFont(IFont font){
  268. if (this.characterList != null) {
  269. this.font = font;
  270. this.fontHeight = font.getFontAbsoluteHeight();
  271. this.isBitmapFont = (font instanceof BitmapFont);
  272. this.updateLayout();
  273. }
  274. }
  275. @Override
  276. public void updateComponent(long timeDelta) {
  277. super.updateComponent(timeDelta);
  278. if (enableCaret){
  279. caretTimeCounter+=timeDelta;
  280. if (caretTimeCounter >= showCaretTime && !showCaret){
  281. showCaret = true;
  282. caretTimeCounter = 0;
  283. }else if (caretTimeCounter >= showCaretTime && showCaret){
  284. showCaret = false;
  285. caretTimeCounter = 0;
  286. }
  287. }
  288. }
  289. @Override
  290. public void preDraw(PGraphics graphics) {
  291. super.preDraw(graphics);
  292. //Hack for drawing anti aliased stroke outline over the clipped area
  293. noStrokeSettingSaved = this.isNoStroke();
  294. if (this.mode == MODE_WRAP && this.getClip() != null && !this.isNoStroke()){
  295. this.setNoStroke(true);
  296. }
  297. }
  298. //FIXME TEST Align/round text with screen pixels
  299. private boolean textPositionRounding = true;
  300. private boolean snapVectorDirty = false;
  301. private Vector3D defaultScale = new Vector3D(1,1,1);
  302. private Vector3D globalTranslation = new Vector3D();
  303. private Vector3D rounded = new Vector3D();
  304. private float tolerance = 0.05f;
  305. private boolean isBitmapFont = false;
  306. private Vector3D diff = new Vector3D(0,0,0);
  307. public void setTextPositionRounding(boolean snap){
  308. this.textPositionRounding = snap;
  309. }
  310. public boolean isTextPositionRounding(){
  311. return this.textPositionRounding;
  312. }
  313. @Override
  314. public void setMatricesDirty(boolean baseMatrixDirty) {
  315. super.setMatricesDirty(baseMatrixDirty);
  316. if (baseMatrixDirty)
  317. snapVectorDirty = baseMatrixDirty;
  318. }
  319. private boolean useDisplayList = false;
  320. private boolean contentDisplayListDirty = true;
  321. private void setContentDisplayListDirty(boolean dirty){
  322. this.contentDisplayListDirty = dirty;
  323. this.useDisplayList = (!this.contentDisplayListDirty);
  324. }
  325. private int displayListID = 0;
  326. public int useContentDisplayList(){
  327. if (enableCaret)
  328. return -1;
  329. GL gl = GLU.getCurrentGL();
  330. //Delete old one
  331. if (this.displayListID != 0){
  332. gl.glDeleteLists(this.displayListID, 1);
  333. }
  334. //Create new list
  335. int listIDFill = gl.glGenLists(1);
  336. if (listIDFill == 0){
  337. System.err.println("Failed to create fill display list");
  338. }
  339. int thisLineTotalXAdvancement = 0;
  340. int lastXAdvancement = innerPaddingLeft;
  341. //To set caret at most left start pos when charlist empty (looks better)
  342. if (enableCaret && showCaret && characterList.size() == 1){
  343. lastXAdvancement = 0;
  344. }
  345. //Record list
  346. gl.glNewList(listIDFill, GL.GL_COMPILE);
  347. drawCharactersGL(gl, characterList, characterList.size(), lastXAdvancement, thisLineTotalXAdvancement);
  348. gl.glEndList();
  349. if (listIDFill != 0){
  350. useDisplayList = true;
  351. displayListID = listIDFill;
  352. }
  353. this.setContentDisplayListDirty(false);
  354. return (listIDFill == 0)? -1 : listIDFill;
  355. }
  356. @Override
  357. protected void destroyComponent() {
  358. super.destroyComponent();
  359. if (MT4jSettings.getInstance().isOpenGlMode() && this.displayListID != 0){
  360. GL gl = GLU.getCurrentGL();
  361. //Delete old one
  362. if (gl != null){
  363. gl.glDeleteLists(this.displayListID, 1);
  364. }
  365. }
  366. }
  367. @Override
  368. public void drawComponent(PGraphics g) {
  369. super.drawComponent(g);
  370. //FIXME snapping wont be useful if textarea is created at non-integer value!? and if Camera isnt default camera
  371. //if global matrix set dirty and comp not scaled -> calculate new diff vector -> apply
  372. //if snap enabled -> apply diff vector
  373. boolean applySnap = false;
  374. if (isBitmapFont && textPositionRounding){
  375. if (snapVectorDirty){ //Calc new snap vector
  376. Matrix m = this.getGlobalMatrix();
  377. if (m.getScale().equalsVectorWithTolerance(defaultScale, tolerance)){ //Only if no scale applied
  378. applySnap = true;
  379. globalTranslation.setXYZ(m.m03, m.m13, m.m23);
  380. rounded.setXYZ(Math.round(globalTranslation.x), Math.round(globalTranslation.y), Math.round(globalTranslation.z));
  381. // rounded.setXYZ((int)globalTranslation.x, (int)globalTranslation.y, (int)globalTranslation.z);
  382. rounded.subtractLocal(globalTranslation);
  383. diff.setXYZ(rounded.x, rounded.y, rounded.z);
  384. snapVectorDirty = false;
  385. g.pushMatrix();
  386. g.translate(diff.x, diff.y, diff.z);
  387. }else{ //global matrix was set dirty but the textarea is scaled -> dont apply snapvector because it gets blurry anyway if scaled
  388. // snapVectorDirty = false; //because only if scale changes back to 1,1,1 we have to calc new snapvector again
  389. applySnap = false;
  390. }
  391. }else{ //new Snap vector already calculated since global matrix was changed
  392. applySnap = true;
  393. g.pushMatrix();
  394. g.translate(diff.x, diff.y, diff.z);
  395. }
  396. }
  397. //Add caret if its time
  398. if (enableCaret && showCaret){
  399. characterList.add(this.getFont().getFontCharacterByUnicode("|"));
  400. }
  401. int charListSize = characterList.size();
  402. int thisLineTotalXAdvancement = 0;
  403. int lastXAdvancement = innerPaddingLeft;
  404. //Account for TOP inner padding if using WRAP mode -> translate text
  405. switch (this.mode) {
  406. case MODE_EXPAND:
  407. //Dont need to translate for innerpadding TOP because we do that in setHeight() making the whole textarea bigger
  408. g.pushMatrix();
  409. g.translate(0, innerPaddingTop);
  410. break;
  411. case MODE_WRAP:
  412. //Need to translate innerpadding TOP because we shouldnt make the textarea bigger like in expand mode
  413. g.pushMatrix();
  414. g.translate(0, innerPaddingTop);
  415. break;
  416. default:
  417. break;
  418. }
  419. // /*//
  420. //To set caret at most left start pos when charlist empty (looks better)
  421. if (enableCaret && showCaret && charListSize == 1){
  422. lastXAdvancement = 0;
  423. }
  424. // */
  425. if (this.isUseDirectGL()){
  426. GL gl = Tools3D.beginGL(pa);
  427. if (totalScrollTextX != 0.0f || totalScrollTextY != 0.0f){
  428. gl.glTranslatef(totalScrollTextX, totalScrollTextY + font.getFontMaxAscent(), 0);
  429. }else{
  430. gl.glTranslatef(0, font.getFontMaxAscent(), 0);
  431. }
  432. /*
  433. //Disabled so that no new list is created everytime something changes
  434. if (!enableCaret && useDisplayList && this.contentDisplayListDirty){
  435. //Re-Create displaylist
  436. this.useContentDisplayList();
  437. }
  438. */
  439. //TODO avoid many stateChanges
  440. //in bitmap font mode for example:
  441. //enable textures, enable vertex arrays and color only once!
  442. if(!enableCaret && useDisplayList && this.displayListID != 0){
  443. gl.glCallList(this.displayListID);
  444. }else{
  445. drawCharactersGL(gl, characterList, charListSize, lastXAdvancement, thisLineTotalXAdvancement);
  446. }
  447. Tools3D.endGL(pa);
  448. }
  449. else{ //P3D rendering
  450. g.pushMatrix(); //FIXME TEST text scrolling - but IMHO better done with parent list/scroll container
  451. g.translate(totalScrollTextX, totalScrollTextY + font.getFontMaxAscent(), 0);
  452. //Set the color for all characters (since the characters dont set their own fill/stroke color anymore
  453. MTColor fillColor = this.getFont().getFillColor();
  454. g.fill(fillColor.getR(), fillColor.getG(), fillColor.getB(), fillColor.getAlpha());
  455. g.stroke(fillColor.getR(), fillColor.getG(), fillColor.getB(), fillColor.getAlpha());
  456. g.tint(fillColor.getR(), fillColor.getG(), fillColor.getB(), fillColor.getAlpha());
  457. for (int i = 0; i < charListSize; i++) {
  458. IFontCharacter character = characterList.get(i);
  459. //Step to the right by the amount of the last characters x advancement
  460. pa.translate(lastXAdvancement, 0, 0); //original
  461. //Save total amount gone to the right in this line
  462. thisLineTotalXAdvancement += lastXAdvancement;
  463. lastXAdvancement = 0;
  464. //Draw the letter
  465. character.drawComponent(g);
  466. //Check if newLine occurs, goto start at new line
  467. if (character.getUnicode().equals("\n")){
  468. pa.translate(-thisLineTotalXAdvancement, fontHeight, 0);
  469. thisLineTotalXAdvancement = 0;
  470. lastXAdvancement = innerPaddingLeft;
  471. }else{
  472. //If caret is showing and we are at index one before caret calc the advancement
  473. if (enableCaret && showCaret && i == charListSize - 2){
  474. if (character.getUnicode().equals("\t")){
  475. lastXAdvancement = character.getHorizontalDist() - character.getHorizontalDist( ) / 20;
  476. }else{
  477. //approximated value, cant get the real one
  478. lastXAdvancement = 2 + character.getHorizontalDist() - (character.getHorizontalDist() / 3);
  479. }
  480. }else{
  481. lastXAdvancement = character.getHorizontalDist();
  482. }
  483. }
  484. }
  485. g.tint(255,255,255,255); //Reset Tint
  486. g.popMatrix();//FIXME TEST text scrolling - but IMHO better done with parent list/scroll container
  487. }
  488. //Innerpadding TOP for wrapped textarea -> translates the text content downwards
  489. switch (this.mode) {
  490. case MODE_EXPAND:
  491. g.popMatrix();
  492. break;
  493. case MODE_WRAP:
  494. //Need to translate innerpadding because we shouldnt make the textarea bigger
  495. g.popMatrix();
  496. break;
  497. default:
  498. break;
  499. }
  500. //remove caret
  501. if (enableCaret && showCaret){
  502. characterList.remove(charListSize-1);
  503. }
  504. //FIXME TEST
  505. if (isBitmapFont && textPositionRounding && applySnap){
  506. g.popMatrix();
  507. }
  508. }
  509. public void setFontColor(MTColor fontColor){
  510. this.getFont().setFillColor(fontColor);
  511. }
  512. private void drawCharactersGL(GL gl, List<IFontCharacter> characterList, int charListSize, int lastXAdv, int lineTotalAdv){
  513. int lastXAdvancement = lastXAdv;
  514. int thisLineTotalXAdvancement = lineTotalAdv;
  515. //Set the color for all characters (since the characters dont set their own fill/stroke color anymore
  516. MTColor fillColor = this.getFont().getFillColor();
  517. gl.glColor4f(fillColor.getR()/255f, fillColor.getG()/255f, fillColor.getB()/255f, fillColor.getAlpha()/255f);
  518. for (int i = 0; i < charListSize; i++) {
  519. IFontCharacter character = characterList.get(i);
  520. //Step to the right by the amount of the last characters x advancement
  521. gl.glTranslatef(lastXAdvancement, 0, 0);
  522. //Save total amount gone to the right in this line
  523. thisLineTotalXAdvancement += lastXAdvancement;
  524. lastXAdvancement = 0;
  525. //Draw the letter
  526. character.drawComponent(gl);
  527. //Check if newLine occurs, goto start at new line
  528. if (character.getUnicode().equals("\n")){
  529. gl.glTranslatef(-thisLineTotalXAdvancement, fontHeight, 0);
  530. thisLineTotalXAdvancement = 0;
  531. lastXAdvancement = innerPaddingLeft;
  532. }else{
  533. //If caret is showing and we are at index one before caret calc the advancement to include the caret in the text area
  534. if (enableCaret && showCaret && i == charListSize-2){
  535. if (character.getUnicode().equals("\t")){
  536. lastXAdvancement = character.getHorizontalDist() - character.getHorizontalDist() / 20;
  537. }else{
  538. //approximated value, cant get the real one
  539. lastXAdvancement = 2 + character.getHorizontalDist() - (character.getHorizontalDist() / 3);
  540. }
  541. }else{
  542. lastXAdvancement = character.getHorizontalDist();
  543. }
  544. }
  545. }
  546. }
  547. private boolean noStrokeSettingSaved;
  548. @Override
  549. public void postDraw(PGraphics graphics) {
  550. super.postDraw(graphics);
  551. //Hack for drawing anti aliased stroke outline over the clipped area
  552. if (this.mode == MODE_WRAP && this.getClip()!= null && !noStrokeSettingSaved){
  553. this.setNoStroke(noStrokeSettingSaved);
  554. boolean noFillSavedSetting = this.isNoFill();
  555. this.setNoFill(true);
  556. super.drawComponent(graphics);//Draw only stroke line after we ended clipping do preserve anti aliasing - hack
  557. this.setNoFill(noFillSavedSetting);
  558. }
  559. }
  560. //FIXME TEST scrolling (used in MTTextField for example)
  561. protected void scrollTextX(float amount){
  562. this.totalScrollTextX += amount;
  563. }
  564. protected void scrollTextY(float amount){
  565. this.totalScrollTextY += amount;
  566. }
  567. protected float getScrollTextX() {
  568. return this.totalScrollTextX;
  569. }
  570. protected float getScrollTextY() {
  571. return this.totalScrollTextY;
  572. }
  573. //FIXME TEST ?
  574. /**
  575. * Changes the texture filtering for the textarea's bitmap font.
  576. * (if a bitmap font is used).
  577. * If the parameter is "true" this will allow the text being scaled without getting
  578. * too pixelated. If the text isnt going to be scaled ever, it is best to leave or
  579. * set this to "false" for a sharper text.
  580. * <br>NOTE: Only applies if OpenGL is the renderer and the textarea uses a bitmap font.
  581. * <br>NOTE: This affects the whole bitmap font so if it is used elsewhere it is changed
  582. * there, too.
  583. *
  584. * @param scalable the new bitmap font scalable
  585. */
  586. public void setBitmapFontTextureFiltered(boolean scalable){
  587. if (MT4jSettings.getInstance().isOpenGlMode() && this.getFont() instanceof BitmapFont){
  588. BitmapFont font = (BitmapFont)this.getFont();
  589. IFontCharacter[] characters = font.getCharacters();
  590. for (IFontCharacter fontCharacter : characters) {
  591. if (fontCharacter instanceof BitmapFontCharacter) {
  592. BitmapFontCharacter bChar = (BitmapFontCharacter) fontCharacter;
  593. bChar.setTextureFiltered(scalable);
  594. }
  595. }
  596. }
  597. }
  598. /**
  599. * Sets the width local.
  600. *
  601. * @param width the new width local
  602. */
  603. @Override
  604. public void setWidthLocal(float width){
  605. super.setWidthLocal(width);
  606. switch (this.mode) {
  607. case MODE_EXPAND:
  608. break;
  609. case MODE_WRAP:
  610. //if in MODE_WRAP also reset the size of the CLIP SHAPE!
  611. if (MT4jSettings.getInstance().isOpenGlMode() && this.getClip() != null && this.getClip().getClipShape() instanceof MTRectangle){
  612. MTRectangle clipRect = (MTRectangle)this.getClip().getClipShape();
  613. // clipRect.setWidthLocal(this.getWidthXY(TransformSpace.LOCAL));
  614. //Clip the text to the area
  615. // this.setClip(new Clip(pApplet, this.getVerticesLocal()[0].x, this.getVerticesLocal()[0].y, this.getWidthXY(TransformSpace.LOCAL), this.getHeightXY(TransformSpace.LOCAL)));
  616. // clipRect.setVertices(Vertex.getDeepVertexArrayCopy(this.getVerticesLocal()));
  617. clipRect.setVertices(this.getVerticesLocal());
  618. }
  619. this.updateLayout();
  620. break;
  621. default:
  622. break;
  623. }
  624. }
  625. /**
  626. * Sets the height local.
  627. *
  628. * @param height the new height local
  629. */
  630. @Override
  631. public void setHeightLocal(float height){
  632. Vertex[] v = this.getVerticesLocal();
  633. switch (this.mode) {
  634. case MODE_EXPAND:
  635. this.setVertices(new Vertex[]{
  636. 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()),
  637. 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()),
  638. 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()),
  639. 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()),
  640. 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()),
  641. });
  642. break;
  643. case MODE_WRAP:
  644. super.setHeightLocal(height);
  645. //if in MODE_WRAP also reset the size of the CLIP SHAPE!
  646. if (MT4jSettings.getInstance().isOpenGlMode() && this.getClip() != null && this.getClip().getClipShape() instanceof MTRectangle){
  647. MTRectangle clipRect = (MTRectangle)this.getClip().getClipShape();
  648. // clipRect.setVertices(Vertex.getDeepVertexArrayCopy(this.getVerticesLocal()));
  649. clipRect.setVertices(this.getVerticesLocal());
  650. }
  651. this.updateLayout();
  652. break;
  653. default:
  654. break;
  655. }
  656. }
  657. /**
  658. * Returns the currently active expand direction of the text area.
  659. * (Only has an impact if the wrap mode of the textarea equals MODE_EXPAND!)
  660. *
  661. * @return the active expand direction
  662. */
  663. public ExpandDirection getExpandDirection() {
  664. return expandDirection;
  665. }
  666. /**
  667. * Sets the expand direction to be used by the text area if a new line is added.
  668. *(Only has an impact if the wrap mode of the textarea equals MODE_EXPAND!)
  669. * @see {@link ExpandDirection}
  670. * @param direction the expand direction to be used
  671. */
  672. public void setExpandDirection(ExpandDirection direction) {
  673. expandDirection = direction;
  674. this.updateLayout(); //This wont translate the area to its original place if expand mode was UP and is now down..
  675. }
  676. @Override
  677. public void setSizeLocal(float width, float height) {
  678. if (width > 0 && height > 0){
  679. Vertex[] v = this.getVerticesLocal();
  680. switch (this.mode) {
  681. case MODE_EXPAND:
  682. this.setVertices(new Vertex[]{
  683. 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()),
  684. 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()),
  685. new Vertex(v[0].x+width, height + (2 * innerPaddingTop), v[2].getTexCoordV(), v[2].getR(), v[2].getG(), v[2].getB(), v[2].getA()),
  686. 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()),
  687. 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()),
  688. });
  689. break;
  690. case MODE_WRAP:
  691. super.setSizeLocal(width, height);
  692. //if in MODE_WRAP also reset the size of the CLIP SHAPE!
  693. if (MT4jSettings.getInstance().isOpenGlMode() && this.getClip() != null && this.getClip().getClipShape() instanceof MTRectangle){
  694. MTRectangle clipRect = (MTRectangle)this.getClip().getClipShape();
  695. //clipRect.setVertices(Vertex.getDeepVertexArrayCopy(this.getVerticesLocal()));
  696. clipRect.setVertices(this.getVerticesLocal());
  697. }
  698. this.updateLayout();
  699. break;
  700. default:
  701. break;
  702. }
  703. }
  704. }
  705. /**
  706. * Appends the string to the textarea.
  707. *
  708. * @param string the string
  709. */
  710. synchronized public void appendText(String string){
  711. for (int i = 0; i < string.length(); i++) {
  712. appendCharByUnicode(string.substring(i, i+1));
  713. }
  714. }
  715. /**
  716. * Sets the provided string as the text of this textarea.
  717. *
  718. * @param string the string
  719. */
  720. synchronized public void setText(String string){
  721. clear();
  722. for (int i = 0; i < string.length(); i++) {
  723. appendCharByUnicode(string.substring(i, i+1));
  724. }
  725. //FIXME TEST
  726. /*
  727. if (MT4jSettings.getInstance().isOpenGlMode()){
  728. if (getRenderer() instanceof MTApplication) {
  729. MTApplication app = (MTApplication) getRenderer();
  730. if (app.isRenderThreadCurrent()){
  731. this.useContentDisplayList();
  732. }else{
  733. app.invokeLater(new Runnable() {
  734. public void run() {
  735. useContentDisplayList();
  736. }
  737. });
  738. }
  739. }else{
  740. this.useContentDisplayList();
  741. }
  742. }
  743. */
  744. }
  745. /* (non-Javadoc)
  746. * @see org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener#getText()
  747. */
  748. public String getText(){
  749. String returnString = "";
  750. for (IFontCharacter character : this.characterList) {
  751. String unicode = character.getUnicode();
  752. if (!character.equals(MTTextArea.artificialLineBreak)) {
  753. returnString += unicode;
  754. }
  755. }
  756. return returnString;
  757. }
  758. /**
  759. * Append char by name.
  760. * @param characterName the character name
  761. */
  762. synchronized public void appendCharByName(String characterName){
  763. //Get the character from the font
  764. IFontCharacter character = font.getFontCharacterByName(characterName);
  765. if (character == null){
  766. System.err.println("Error adding character with name '" + characterName + "' to the textarea. The font couldnt find the character. -> Trying to use 'missing glyph'");
  767. character = font.getFontCharacterByName("missing-glyph");
  768. if (character != null)
  769. addCharacter(character);
  770. }else{
  771. addCharacter(character);
  772. }
  773. }
  774. /* (non-Javadoc)
  775. * @see org.mt4j.components.visibleComponents.widgets.keyboard.ITextInputListener#appendCharByUnicode(java.lang.String)
  776. */
  777. synchronized public void appendCharByUnicode(String unicode){
  778. //Get the character from the font
  779. IFontCharacter character = font.getFontCharacterByUnicode(unicode);
  780. if (character == null){
  781. // System.err.println("Error adding character with unicode '" + unicode + "' to the textarea. The font couldnt find the character. ->Trying to use 'missing glyph'");
  782. character = font.getFontCharacterByUnicode("missing-glyph");
  783. if (character != null)
  784. addCharacter(character);
  785. }else{
  786. addCharacter(character);
  787. }
  788. }
  789. /**
  790. * Gets the characters. Also returns articifially added new line characters that were
  791. * added by the MTTextArea
  792. * @return the characters
  793. */
  794. public IFontCharacter[] getCharacters(){
  795. return this.characterList.toArray(new IFontCharacter[this.characterList.size()]);
  796. }
  797. /**
  798. * Adds the character.
  799. *
  800. * @param character the character
  801. */
  802. private void addCharacter(IFontCharacter character){
  803. this.characterList.add(character);
  804. this.characterAdded(character);
  805. this.setContentDisplayListDirty(true);
  806. }
  807. /**
  808. * Invoked everytime a character is added.
  809. *
  810. * @param character the character
  811. */
  812. protected void characterAdded(IFontCharacter character){
  813. switch (this.mode) {
  814. case MODE_EXPAND:
  815. if (character.getUnicode().equals("\n")){
  816. //Expand vertically at enter
  817. this.setHeightLocal(this.getTotalLinesHeight());
  818. //Moves the Textarea up at a enter character instead of down
  819. if (getExpandDirection() == ExpandDirection.UP)
  820. this.translate(new Vector3D(0, -fontHeight, 0));
  821. }else{
  822. //Expand the textbox to the extend of the widest line width
  823. this.setWidthLocal(getMaxLineWidth());
  824. }
  825. break;
  826. case MODE_WRAP:
  827. float localWidth = this.getWidthXY(TransformSpace.LOCAL);
  828. // float maxLineWidth = this.getMaxLineWidth();
  829. float maxLineWidth = this.getLastLineWidth();
  830. if (this.characterList.size() > 0 && maxLineWidth > localWidth ) {
  831. // if (this.characterList.size() > 0 && maxLineWidth > (localWidth - 2 * this.getInnerPaddingLeft())) {
  832. // this.characterList.add(this.characterList.size() -1 , this.font.getFontCharacterByUnicode("\n"));
  833. try {
  834. int lastSpacePos = getLastWhiteSpace();
  835. if (lastSpacePos != -1 ){ //&& !this.characterList.get(characterList.size()-1).getUnicode().equals("\n")
  836. // this.characterList.add(lastSpacePos + 1, this.font.getFontCharacterByUnicode("\n"));
  837. this.characterList.add(lastSpacePos + 1, MTTextArea.artificialLineBreak);
  838. }else{
  839. return;
  840. }
  841. } catch (Exception e) {
  842. e.printStackTrace();
  843. }
  844. }
  845. break;
  846. default:
  847. break;
  848. }
  849. }
  850. private int getLastWhiteSpace(){
  851. for (int i = this.characterList.size()-1; i > 0; i--) {
  852. IFontCharacter character = this.characterList.get(i);
  853. if (character.getUnicode().equals(" ")){
  854. return i;
  855. }else if (character.getUnicode().equals("\n")){// stop search when newline found before first whitespace
  856. return -1;
  857. }
  858. }
  859. return -1;
  860. }
  861. /**
  862. * When Character removed.
  863. *
  864. * @param character the character
  865. */
  866. protected void characterRemoved(IFontCharacter character){
  867. switch (this.mode) {
  868. case MODE_EXPAND:
  869. //Resize text field
  870. if (character.getUnicode().equals("\n")){
  871. //Reduce field vertically at enter
  872. this.setHeightLocal(this.getTotalLinesHeight());
  873. //makes the textarea go down when a line is removed instead staying at the same loc.
  874. if (getExpandDirection() == ExpandDirection.UP)
  875. translate(new Vector3D(0, fontHeight, 0));
  876. }else{
  877. //Reduce field horizontally
  878. this.setWidthLocal(getMaxLineWidth());
  879. }
  880. break;
  881. case MODE_WRAP:
  882. break;
  883. default:
  884. break;
  885. }
  886. }
  887. /**
  888. * resets the textarea, clears all characters.
  889. */
  890. public void clear(){
  891. while (!characterList.isEmpty()){
  892. removeLastCharacter();
  893. }
  894. }
  895. /**
  896. * Removes the last character in the textarea.
  897. */
  898. synchronized public void removeLastCharacter(){
  899. if (this.characterList.isEmpty())
  900. return;
  901. //REMOVE THE CHARACTER
  902. IFontCharacter lastCharacter = this.characterList.get(this.characterList.size()-1);
  903. this.characterList.remove(this.characterList.size()-1);
  904. this.characterRemoved(lastCharacter);
  905. this.setContentDisplayListDirty(true);
  906. }
  907. /**
  908. * Gets the last line width.
  909. *
  910. * @return the last line width
  911. */
  912. protected float getLastLineWidth(){
  913. float currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
  914. for (IFontCharacter character : this.characterList) {
  915. if (character.getUnicode().equals("\n")) {
  916. currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
  917. } else {
  918. currentLineWidth += character.getHorizontalDist();
  919. }
  920. }
  921. return currentLineWidth;
  922. }
  923. /**
  924. * Gets the max line width. The padding is also added.
  925. *
  926. * @return the max line width
  927. */
  928. protected float getMaxLineWidth(){
  929. float currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
  930. float maxWidth = currentLineWidth;
  931. for (IFontCharacter character : this.characterList) {
  932. if (character.getUnicode().equals("\n")) {
  933. if (currentLineWidth > maxWidth) {
  934. maxWidth = currentLineWidth;
  935. }
  936. currentLineWidth = 2 * this.getInnerPaddingLeft() + caretWidth;
  937. } else {
  938. currentLineWidth += character.getHorizontalDist();
  939. if (currentLineWidth > maxWidth) {
  940. maxWidth = currentLineWidth;
  941. }
  942. }
  943. }
  944. return maxWidth;
  945. }
  946. /**
  947. * Gets the total lines height. Padding is not included
  948. *
  949. * @return the total lines height
  950. */
  951. protected float getTotalLinesHeight(){
  952. float height = fontHeight ;//
  953. for (IFontCharacter character : this.characterList) {
  954. if (character.getUnicode().equals("\n")) {
  955. height += fontHeight;
  956. }
  957. }
  958. return height;
  959. }
  960. /**
  961. * Sets the padding (Top: 5 + value, Left: 8 + value)
  962. * @param padding
  963. */
  964. public void setPadding(float padding) {
  965. innerPaddingTop = 5 + (int)padding;
  966. innerPaddingLeft = 8 + (int)padding;
  967. this.updateLayout();
  968. }
  969. public void setInnerPadding(int innerPadding){
  970. this.setInnerPaddingTop(innerPadding);
  971. this.setInnerPaddingLeft(innerPadding);
  972. }
  973. public float getInnerPaddingTop() {
  974. return this.innerPaddingTop;
  975. }
  976. public void setInnerPaddingTop(int innerPaddingTop) {
  977. this.innerPaddingTop = innerPaddingTop;
  978. switch (this.mode) {
  979. case MODE_EXPAND:
  980. //At MODE_EXPAND we re-set the text so the size gets re-calculated
  981. //We can safely do this since in EXPAND mode we didnt add any artificial control characters
  982. this.updateLayout();
  983. break;
  984. case MODE_WRAP:
  985. //At MODE_WRAP the padding is done with gl_Translate calls so we dont have to reset the size
  986. //TODO also reset? this.setText(this.getText());?
  987. break;
  988. default:
  989. break;
  990. }
  991. }
  992. public float getInnerPaddingLeft() {
  993. return this.innerPaddingLeft;
  994. }
  995. public void setInnerPaddingLeft(int innerPaddingLeft) {
  996. this.innerPaddingLeft = innerPaddingLeft;
  997. switch (this.mode) {
  998. case MODE_EXPAND:
  999. //At MODE_EXPAND we re-set the text so the size gets re-calculated
  1000. //We can safely do this since in EXPAND mode we didnt add any artificial control characters
  1001. this.updateLayout();
  1002. break;
  1003. case MODE_WRAP:
  1004. // WE HAVE TO RESET THE ORIGINAL TEXT BECAUSE WE BREAK THE LINE AT DIFFERENT POSITIONS IF THE INNERPADDING IS CHANGED!
  1005. this.updateLayout();
  1006. break;
  1007. default:
  1008. break;
  1009. }
  1010. }
  1011. /**
  1012. * Updates layout. (just does this.setText(this.getText()))
  1013. */
  1014. protected void updateLayout(){
  1015. if (this.mode == MODE_EXPAND){
  1016. this.setHeightLocal(this.getTotalLinesHeight());
  1017. this.setWidthLocal(getMaxLineWidth());
  1018. }
  1019. this.setText(this.getText());
  1020. }
  1021. /**
  1022. * Gets the line count.
  1023. *
  1024. * @return the line count
  1025. */
  1026. public int getLineCount(){
  1027. int count = 0;
  1028. for (IFontCharacter character : this.characterList) {
  1029. if (character.getUnicode().equals("\n")) {
  1030. count++;
  1031. }
  1032. }
  1033. return count;
  1034. }
  1035. /**
  1036. * Gets the font.
  1037. *
  1038. * @return the font
  1039. */
  1040. public IFont getFont() {
  1041. return font;
  1042. }
  1043. /**
  1044. * Snap to keyboard.
  1045. *
  1046. * @param mtKeyboard the mt keyboard
  1047. */
  1048. public void snapToKeyboard(MTKeyboard mtKeyboard){
  1049. //OLD WAY
  1050. // this.translate(new Vector3D(30, -(getFont().getFontAbsoluteHeight() * (getLineCount())) + getFont().getFontMaxDescent() - borderHeight, 0));
  1051. mtKeyboard.addChild(this);
  1052. this.setPositionRelativeToParent(new Vector3D(40, -this.getHeightXY(TransformSpace.LOCAL)*0.5f));
  1053. }
  1054. public boolean isSelected() {
  1055. // TODO Auto-generated method stub
  1056. return false;
  1057. }
  1058. public void setSelected(boolean selected) {
  1059. // TODO Auto-generated method stub
  1060. }
  1061. /**
  1062. * Checks if is enable caret.
  1063. *
  1064. * @return true, if is enable caret
  1065. */
  1066. public boolean isEnableCaret() {
  1067. return enableCaret;
  1068. }
  1069. /**
  1070. * Sets the enable caret.
  1071. *
  1072. * @param enableCaret the new enable caret
  1073. */
  1074. public void setEnableCaret(boolean enableCaret) {
  1075. if (this.getFont().getFontCharacterByUnicode("|") != null){
  1076. this.enableCaret = enableCaret;
  1077. if (enableCaret){
  1078. this.caretWidth = 10;
  1079. }else{
  1080. this.caretWidth = 0;
  1081. }
  1082. if (this.mode == MODE_EXPAND){
  1083. this.setWidthLocal(this.getMaxLineWidth());
  1084. }
  1085. }else{
  1086. System.err.println("Cant enable caret for this textfield, the font doesent include the letter '|'");
  1087. }
  1088. this.setContentDisplayListDirty(true);
  1089. }
  1090. public int compareTo(Object o) {
  1091. if (o instanceof MTTextArea) {
  1092. MTTextArea ta = (MTTextArea)o;
  1093. return this.getText().compareToIgnoreCase(ta.getText());
  1094. } else {
  1095. return 0;
  1096. }
  1097. }
  1098. /**
  1099. * Artifical line break to be used instead of the regular line break
  1100. * to indicate that this linebreak was added by the text area itself for
  1101. * layout reasons and doesent really belong to the supplied text.
  1102. *
  1103. * @author Christopher Ruff
  1104. */
  1105. protected class ArtificalLineBreak implements IFontCharacter{
  1106. public void drawComponent(PGraphics g) {}
  1107. public void drawComponent(GL gl) { }
  1108. public void destroy() { }
  1109. public int getHorizontalDist() {
  1110. return 0;
  1111. }
  1112. public String getUnicode() {
  1113. return "\n";
  1114. }
  1115. }
  1116. @Override
  1117. protected void applyStyleSheetCustom(CSSStyle virtualStyleSheet) {
  1118. super.applyStyleSheetCustom(virtualStyleSheet);
  1119. if (this.getRenderer() instanceof MTApplication) {
  1120. MTApplication app = (MTApplication) this.getRenderer();
  1121. if (!virtualStyleSheet.getFont().equals(
  1122. app.getCssStyleManager().getDefaultFont(app))
  1123. && !this.isIgnoreCSSFont()) {
  1124. this.setFont(virtualStyleSheet.getFont());
  1125. }
  1126. if (virtualStyleSheet.isModifiedPaddingWidth()) {
  1127. this.setPadding(virtualStyleSheet.getPaddingWidth());
  1128. }
  1129. }
  1130. }
  1131. }