/* Part of the GUI library for Processing http://www.lagers.org.uk/g4p/index.html http://sourceforge.net/projects/g4p/files/?source=navbar Copyright (c) 2012 Peter Lager This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package automenta.vivisect.gui; import automenta.vivisect.gui.HotSpot.HSrect; import automenta.vivisect.gui.StyledString.TextLayoutHitInfo; import automenta.vivisect.gui.StyledString.TextLayoutInfo; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.font.TextAttribute; import java.awt.font.TextHitInfo; import java.awt.font.TextLayout; import java.awt.geom.GeneralPath; import java.util.LinkedList; import processing.core.PApplet; import processing.core.PGraphics; import processing.core.PGraphicsJava2D; import processing.event.MouseEvent; /** * The text area component. <br> * * This control allows the user to enter and edit multiple lines of text. The control * also allows default text, horizontal and vertical scrollbars. <br> * * Enables user to enter text at runtime. Text can be selected using the mouse * or keyboard shortcuts and then copied or cut to the clipboard. Text * can also be pasted in. <br> * * There are some methods to add and clear text attributes to all or some of the * text in the control. If a method is expecting a line number, you should specify * the actual line number for the entire text (lines numbers start at 0). It is * not the line number in the visible display because this can change if the text * has been scrolled vertically. <br> * * * Fires SELECTION_CHANGED, CHANGED, ENTERED, LOST_FOCUS, GETS_FOCUS events.<br> * The focus events are only fired if the control is added to a GTabManager object. <br> * * @author Peter Lager * */ public class GTextArea extends GEditableTextControl { protected boolean newline = false, backspace = false; /** * Create a text area without scrollbars and a text wrap width to fit the control. * * @param theApplet * @param p0 * @param p1 * @param p2 * @param p3 */ public GTextArea(PApplet theApplet, float p0, float p1, float p2, float p3) { this(theApplet, p0, p1, p2, p3, SCROLLBARS_NONE, Integer.MAX_VALUE); } /** * Create a text field with the given scrollbar policy and a text wrap width to fit the control. <br> * The scrollbar policy can be one of these <br> * <ul> * <li>SCROLLBARS_NONE</li> * <li>SCROLLBARS_HORIZONTAL_ONLY</li> * <li>SCROLLBARS_VERTICAL_ONLY</li> * <li>SCROLLBARS_BOTH</li> * </ul> * If you want the scrollbar to auto hide then perform a logical or with * <ul> * <li>SCROLLBARS_AUTOHIDE</li> * </ul> * e.g. SCROLLBARS_BOTH | SCROLLBARS_AUTOHIDE * <br> * @param theApplet * @param p0 * @param p1 * @param p2 * @param p3 * @param sbPolicy */ public GTextArea(PApplet theApplet, float p0, float p1, float p2, float p3, int sbPolicy) { this(theApplet, p0, p1, p2, p3, sbPolicy, Integer.MAX_VALUE); } /** * Create a text field with the given scrollbar policy with a user specified text wrap length <br> * * @param theApplet * @param p0 * @param p1 * @param p2 * @param p3 * @param sbPolicy * @param wrapWidth */ public GTextArea(PApplet theApplet, float p0, float p1, float p2, float p3, int sbPolicy, int wrapWidth) { super(theApplet, p0, p1, p2, p3, sbPolicy); children = new LinkedList<GControl>(); tx = ty = TPAD6; tw = width - 2 * TPAD6 - ((scrollbarPolicy & SCROLLBAR_VERTICAL) != 0 ? 18 : 0); th = height - 2 * TPAD6 - ((scrollbarPolicy & SCROLLBAR_HORIZONTAL) != 0 ? 18 : 0); // The text wrap width is based on the width of the text display area unless // some other value is specified. this.wrapWidth = (wrapWidth == Integer.MAX_VALUE) ? (int)tw : wrapWidth; // Clip zone gpTextDisplayArea = new GeneralPath(); gpTextDisplayArea.moveTo( 0, 0); gpTextDisplayArea.lineTo( 0, th); gpTextDisplayArea.lineTo(tw, th); gpTextDisplayArea.lineTo(tw, 0); gpTextDisplayArea.closePath(); // // The image buffer is just for the typing area // buffer = (PGraphicsJava2D) winApp.createGraphics((int)width, (int)height, PApplet.JAVA2D); // buffer.rectMode(PApplet.CORNER); // buffer.g2.setFont(localFont); hotspots = new HotSpot[]{ new HSrect(1, tx, ty, tw, th), // typing area new HSrect(9, 0, 0, width, height) // control surface }; GUI.pushStyle(); GUI.showMessages = false; z = Z_STICKY; GUI.control_mode = GControlMode.CORNER; if((scrollbarPolicy & SCROLLBAR_HORIZONTAL) != 0){ hsb = new GScrollbar(theApplet, 0, 0, tw, 16); add(hsb, tx, ty + th + 2, 0); hsb.addEventHandler(this, "hsbEventHandler"); hsb.setAutoHide(autoHide); } if((scrollbarPolicy & SCROLLBAR_VERTICAL) != 0){ vsb = new GScrollbar(theApplet, 0, 0, th, 16); add(vsb, tx + tw + 18, ty, PI/2); vsb.addEventHandler(this, "vsbEventHandler"); vsb.setAutoHide(autoHide); } GUI.popStyle(); setText("", (int)tw); createEventHandler(GUI.applet, "handleTextEvents", new Class<?>[]{ GEditableTextControl.class, GEvent.class }, new String[]{ "textcontrol", "event" } ); registeredMethods = PRE_METHOD | DRAW_METHOD | MOUSE_METHOD | KEY_METHOD; GUI.addControl(this); } /** * Set the text to be used. The wrap width is determined by the current * text wrapwidth or if there is no text then the text width * of the control. * * @param text to be displayed */ public void setText(String text){ if(stext == null) setText(text, wrapWidth); else setText(text, stext.getWrapWidth()); bufferInvalid = true; } /** * Set the text to display and adjust any scrollbars * @param text text to display * @param wrapWidth the wrap width */ public void setText(String text, int wrapWidth){ setStyledText(new StyledString(text, wrapWidth)); bufferInvalid = true; } /** * Set the text to display and adjust any scrollbars * @param lines an array of Strings representing the text to display */ public void setText(String[] lines){ if(lines != null){ setText(PApplet.join(lines, "\n")); bufferInvalid = true; } } /** * Set the text to display and adjust any scrollbars * @param lines an array of Strings representing the text to display * @param wrapWidth the wrap width */ public void setText(String[] lines, int wrapWidth){ if(lines != null){ setText(PApplet.join(lines, "\n"), wrapWidth); bufferInvalid = true; } } /** * Get the text as a String array. (splitting on line breaks). * * @return the associated plain text as a String array split on line breaks */ public String[] getTextAsArray(){ return stext.getPlainTextAsArray(); } /** * Adds the text attribute to a range of characters on a particular display line. If charEnd * is past the EOL then the attribute will be applied to the end-of-line. * * @param attr the text attribute to add * @param value value of the text attribute * @param lineNo the display line number (starts at 0) * @param charStart the position of the first character to apply the attribute * @param charEnd the position after the last character to apply the attribute */ public void addStyle(TextAttribute attr, Object value, int lineNo, int charStart, int charEnd){ if(stext != null){ stext.addAttribute(attr, value, lineNo, charStart, charEnd); bufferInvalid = true; } } /** * Adds the text attribute to an entire display line. * * @param attr the text attribute to add * @param value value of the text attribute * @param lineNo the display line number (starts at 0) */ public void addStyle(TextAttribute attr, Object value, int lineNo){ if(stext != null){ stext.addAttribute(attr, value, lineNo); bufferInvalid = true; } } /** * Clears all text attribute from a range of characters on a particular display line. * If charEnd is past the EOL then the attributes will be cleared to the * end-of-line. * * @param lineNo the display line number (starts at 0) * @param charStart the position of the first character to apply the attribute * @param charEnd the position after the last character to apply the attribute */ public void clearStyles(int lineNo, int charStart, int charEnd){ if(stext != null){ stext.clearAttributes(lineNo, charStart, charEnd); bufferInvalid = true; } } /** * Clears all text attribute from an entire display line. * * @param lineNo the display line number (starts at 0) */ public void clearStyles(int lineNo){ if(stext != null){ stext.clearAttributes(lineNo); bufferInvalid = true; } } /** * Set the styled text to be displayed. * */ public void setStyledText(StyledString st){ stext = st; if(stext.getWrapWidth() == Integer.MAX_VALUE) stext.setWrapWidth(wrapWidth); else wrapWidth = stext.getWrapWidth(); stext.getLines(buffer.g2); if(stext.getNbrLines() > 0){ endTLHI.tli = stext.getLines(buffer.g2).getFirst(); endTLHI.thi = endTLHI.tli.layout.getNextLeftHit(1); startTLHI.copyFrom(endTLHI); calculateCaretPos(endTLHI); keepCursorInView = true; } ptx = pty = 0; float sTextHeight; // If needed update the vertical scrollbar if(vsb != null){ sTextHeight = stext.getTextAreaHeight(); if(sTextHeight < th) vsb.setValue(0.0f, 1.0f); else vsb.setValue(0, th/sTextHeight); } // If needed update the horizontal scrollbar if(hsb != null){ if(stext.getMaxLineLength() < tw) hsb.setValue(0,1); else hsb.setValue(0, tw/stext.getMaxLineLength()); } bufferInvalid = true; } /** * Add text to the end of the current text. This is useful for a logging' type activity. <br> * * No events will be generated and the caret will be moved to the end of any appended text. <br> * * @param text the text to append * @return true if some characters were added */ public boolean appendText(String text){ // if(text == null || text.equals("")) // return false; // if(stext.insertCharacters(text, stext.length(), true, false) == 0) // return false; if(text == null || text.equals("") || stext.insertCharacters(text, stext.length(), true, false) == 0) return false; LinkedList<TextLayoutInfo> lines = stext.getLines(buffer.g2); endTLHI.tli = lines.getLast(); endTLHI.thi = endTLHI.tli.layout.getNextRightHit(endTLHI.tli.nbrChars - 1); startTLHI.copyFrom(endTLHI); calculateCaretPos(endTLHI); updateScrollbars(lines.getLast().layout.getVisibleAdvance()); bufferInvalid = true; return true; } /** * Insert text at the display position specified. <br> * * The area line number starts at 0 and includes any lines scrolled off the top. So if * three lines have been scrolled off the top the first visible line is number 3. <br> * * No events will be generated and the caret will be moved to the end of any inserted text. <br> * * @param text the text to insert * @param lineNo the area line number * @param charNo the character position to insert text in display line * @return true if some characters were inserted */ public boolean insertText(String text, int lineNo, int charNo){ return insertText(text, lineNo, charNo, false, false); } /** * Insert text at the display position specified. <br> * * The area line number starts at 0 and includes any lines scrolled off the top. So if * three lines have been scrolled off the top the first visible line is number 3. <br> * * No events will be generated and the caret will be moved to the end of any inserted text. <br> * * @param text the text to insert * @param lineNo the area line number * @param charNo the character position to insert text in display line * @param startWithEOL if true,inserted text will start on newline * @param endWithEOL if true, text after inserted text will start on new line * @return true if some characters were inserted */ public boolean insertText(String text, int lineNo, int charNo, boolean startWithEOL, boolean endWithEOL){ if(text != null && text.length() > 0){ int pos = stext.getPos(lineNo, charNo); int change = stext.insertCharacters(text, lineNo, charNo, startWithEOL, endWithEOL); // displayCaretPos("Caret starts at "); if(change != 0){ LinkedList<TextLayoutInfo> lines = stext.getLines(buffer.g2); updateScrollbars(lines.getLast().layout.getVisibleAdvance()); // Move caret to end of insert if possible pos += change; TextLayoutHitInfo tlhi = stext.getTLHIforCharPosition(pos); if(tlhi != null){ endTLHI.copyFrom(tlhi); moveCaretLeft(endTLHI); startTLHI.copyFrom(endTLHI); // displayCaretPos("Caret ends at "); calculateCaretPos(tlhi); keepCursorInView = true; showCaret = true; } bufferInvalid = true; return true; } } return false; } /** * Insert text at the current caret position. If the current caret position is undefined * the text will be inserted at the beginning of the text. <br> * * No events will be generated and the caret will be moved to the end of any inserted text. <br> * * @param text the text to insert * @param startWithEOL if true,inserted text will start on newline * @param endWithEOL if true, text after inserted text will start on new line * @return true if some characters were inserted */ public boolean insertText(String text, boolean startWithEOL, boolean endWithEOL){ int lineNo = 0, charNo = 0; if(endTLHI.tli != null && endTLHI.thi != null){ lineNo = endTLHI.tli.lineNo; charNo = endTLHI.thi.getCharIndex(); } return insertText(text, lineNo, charNo, startWithEOL, endWithEOL); } /** * Insert text at the current caret position. If the current caret position is undefined * the text will be inserted at the beginning of the text. <br> * * No events will be generated and the caret will be moved to the end of any inserted text. <br> * * @param text the text to insert * @return true if some characters were inserted */ public boolean insertText(String text){ return insertText(text, false, false); } // For debugging only @SuppressWarnings("unused") private void displayCaretPos(String title){ if(endTLHI != null && endTLHI.tli != null && endTLHI.thi != null){ System.out.println(title + " :: Carat on line " + endTLHI.tli.lineNo + " at char " + endTLHI.thi.getCharIndex()); } else { System.out.println(title + " :: unknown caret position"); } } private void updateScrollbars(float hvalue){ if(vsb != null){ float vfiller = Math.min(1, th/stext.getTextAreaHeight()); vsb.setValue(1 - vfiller, vfiller); keepCursorInView = true; } // If needed update the horizontal scrollbar if(hsb != null){ //float hvalue = lines.getLast().layout.getVisibleAdvance(); float hlinelength = stext.getMaxLineLength(); float hfiller = Math.min(1, tw/hlinelength); if(caretX < tw) hsb.setValue(0,hfiller); else hsb.setValue(hvalue/hlinelength, hfiller); keepCursorInView = true; } } /** * Get the text on a particular line in the text area. <br> * The line does not need to be visible and the line numbers * always start at 0. <br> * The result is not dependent on what is visible at any * particular time but on the overall position in text area * control. <br> * If the line number is invalid then an empty string is returned. <br> * Trailing EOL characters are removed. * * @param lineNo the text area line number we want * @return the plain text in a display line */ public String getText(int lineNo){ Graphics2D g2d = buffer.g2; // Get the latest lines of text LinkedList<TextLayoutInfo> lines = stext.getLines(g2d); if(lineNo < 0 || lineNo >= lines.size()) return ""; TextLayoutInfo tli = lines.get(lineNo); String s = stext.getPlainText(tli.startCharIndex, tli.startCharIndex + tli.nbrChars); // Strip off trailing EOL int p = s.length() - 1; while(p > 0 && s.charAt(p) == EOL) p--; return (p == s.length() - 1) ? s : s.substring(0, p+1); } /** * Get the length of text on a particular line in the text area. <br> * The line does not need to be visible and the line numbers * always start at 0. <br> * The result is not dependent on what is visible at any * particular time but on the overall position in text area * control. <br> * If ignoreEOL is true then EOL characters are not included in the count. * * @param lineNo the text area line number we want * @param ignoreEOL if true do not include trailing end=of-line characters * @return the length of the line, or <) if the line number is invalid */ public int getTextLength(int lineNo, boolean ignoreEOL){ Graphics2D g2d = buffer.g2; // Get the latest lines of text LinkedList<TextLayoutInfo> lines = stext.getLines(g2d); if(lineNo < 0 || lineNo >= lines.size()) return -1; TextLayoutInfo tli = lines.get(lineNo); // String s = stext.getPlainText(tli.startCharIndex, tli.startCharIndex + tli.nbrChars); String s = stext.getPlainText(); int len = tli.nbrChars; if(ignoreEOL){ // Strip off trailing EOL int p = tli.startCharIndex + tli.nbrChars-1; while(p > tli.startCharIndex && s.charAt(p) == EOL){ p--; len--; } } return len; } /** * If the buffer is invalid then redraw it. */ protected void updateBuffer(){ if(bufferInvalid) { Graphics2D g2d = buffer.g2; // Get the latest lines of text LinkedList<TextLayoutInfo> lines = stext.getLines(g2d); if(lines.isEmpty() && promptText != null) lines = promptText.getLines(g2d); bufferInvalid = false; TextLayoutHitInfo startSelTLHI = null, endSelTLHI = null; buffer.beginDraw(); // Whole control surface if opaque if(opaque) buffer.background(palette[6]); else buffer.background(buffer.color(255,0)); // Now move to top left corner of text display area buffer.translate(tx,ty); // Typing area surface buffer.noStroke(); buffer.fill(palette[7]); buffer.rect(-1,-1,tw+2,th+2); g2d.setClip(gpTextDisplayArea); buffer.translate(-ptx, -pty); // Translate in preparation for display selection and text if(hasSelection()){ if(endTLHI.compareTo(startTLHI) == -1){ startSelTLHI = endTLHI; endSelTLHI = startTLHI; } else { startSelTLHI = startTLHI; endSelTLHI = endTLHI; } } // Display selection and text for(TextLayoutInfo lineInfo : lines){ TextLayout layout = lineInfo.layout; buffer.translate(0, layout.getAscent()); // Draw selection if any if(hasSelection() && lineInfo.compareTo(startSelTLHI.tli) >= 0 && lineInfo.compareTo(endSelTLHI.tli) <= 0 ){ int ss = 0; ss = (lineInfo.compareTo(startSelTLHI.tli) == 0) ? startSelTLHI.thi.getInsertionIndex() : 0; int ee = endSelTLHI.thi.getInsertionIndex(); ee = (lineInfo.compareTo(endSelTLHI.tli) == 0) ? endSelTLHI.thi.getInsertionIndex() : lineInfo.nbrChars-1; g2d.setColor(jpalette[14]); Shape selShape = layout.getLogicalHighlightShape(ss, ee); g2d.fill(selShape); } // display text g2d.setColor(jpalette[2]); lineInfo.layout.draw(g2d, 0, 0); buffer.translate(0, layout.getDescent() + layout.getLeading()); } g2d.setClip(null); buffer.endDraw(); } } public void pre(){ if(keepCursorInView){ boolean horzScroll = false, vertScroll = false; float max_ptx = caretX - tw + 2; float max_pty = caretY - th + 2 * stext.getMaxLineHeight(); if(endTLHI != null){ if(ptx > caretX){ // LEFT? ptx -= HORZ_SCROLL_RATE; if(ptx < 0) ptx = 0; horzScroll = true; } else if(ptx < max_ptx){ // RIGHT? ptx += HORZ_SCROLL_RATE; if(ptx > max_ptx) ptx = max_ptx; horzScroll = true; } if(pty > caretY){ // UP? pty -= VERT_SCROLL_RATE; if(pty < 0) pty = 0; vertScroll = true; } else if(pty < max_pty){ // DOWN? pty += VERT_SCROLL_RATE; vertScroll = true; } if(horzScroll && hsb != null) hsb.setValue(ptx / (stext.getMaxLineLength() + 4)); if(vertScroll && vsb != null) vsb.setValue(pty / (stext.getTextAreaHeight() + 1.5f * stext.getMaxLineHeight())); } // If we have scrolled invalidate the buffer otherwise forget it if(horzScroll || vertScroll) bufferInvalid = true; else keepCursorInView = false; } } public void draw(){ if(!visible) return; // Update buffer if invalid updateBuffer(); winApp.pushStyle(); winApp.pushMatrix(); // Perform the rotation applyTransform(); winApp.pushMatrix(); // Move matrix to line up with top-left corner winApp.translate(-halfWidth, -halfHeight); // Draw buffer winApp.imageMode(PApplet.CORNER); if(alphaLevel < 255) winApp.tint(TINT_FOR_ALPHA, alphaLevel); winApp.image(buffer, 0, 0); // Draw caret if text display area if(focusIsWith == this && showCaret && endTLHI != null){ float[] cinfo = endTLHI.tli.layout.getCaretInfo(endTLHI.thi); float x_left = - ptx + cinfo[0]; float y_top = - pty + endTLHI.tli.yPosInPara; float y_bot = y_top - cinfo[3] + cinfo[5]; if(x_left >= 0 && x_left <= tw && y_top >= 0 && y_bot <= th){ winApp.strokeWeight(1.5f); winApp.stroke(palette[12]); winApp.line(tx+x_left, ty + Math.max(0, y_top), tx+x_left, ty + Math.min(th, y_bot)); } } winApp.popMatrix(); // Draw scrollbars if(children != null){ for(GControl c : children) c.draw(); } winApp.popMatrix(); winApp.popStyle(); } public PGraphics getSnapshot(){ updateBuffer(); PGraphicsJava2D snap = (PGraphicsJava2D) winApp.createGraphics(buffer.width, buffer.height, PApplet.JAVA2D); snap.beginDraw(); snap.image(buffer,0,0); if(hsb != null){ snap.pushMatrix(); snap.translate(hsb.getX(), hsb.getY()); snap.image(hsb.getBuffer(), 0, 0); snap.popMatrix(); } if(vsb != null){ snap.pushMatrix(); snap.translate(vsb.getX(), vsb.getY()); snap.rotate(PApplet.PI/2); snap.image(vsb.getBuffer(), 0, 0); snap.popMatrix(); } snap.endDraw(); return snap; } protected void keyPressedProcess(int keyCode, char keyChar, boolean shiftDown, boolean ctrlDown){ boolean validKeyCombo = true; switch(keyCode){ case LEFT: moveCaretLeft(endTLHI); break; case RIGHT: moveCaretRight(endTLHI); break; case UP: moveCaretUp(endTLHI); break; case DOWN: moveCaretDown(endTLHI); break; case GConstants.HOME: if(ctrlDown) // move to start of text moveCaretStartOfText(endTLHI); else // Move to start of line moveCaretStartOfLine(endTLHI); break; case GConstants.END: if(ctrlDown) // move to end of text moveCaretEndOfText(endTLHI); else // Move to end of line moveCaretEndOfLine(endTLHI); break; case 'A': if(ctrlDown){ moveCaretStartOfText(startTLHI); moveCaretEndOfText(endTLHI); // Make shift down so that the start caret position is not // moved to match end caret position. shiftDown = true; } break; case 'C': if(ctrlDown) GClip.copy(getSelectedText()); validKeyCombo = false; break; case 'V': if(ctrlDown){ String p = GClip.paste(); if(p.length() > 0){ // delete selection and add if(hasSelection()) stext.deleteCharacters(pos, nbr); stext.insertCharacters(p, pos); adjust = p.length(); textChanged = true; } } break; default: validKeyCombo = false; } if(validKeyCombo){ calculateCaretPos(endTLHI); //**************************************************************** // If we have moved to the end of a paragraph marker if(caretX > stext.getWrapWidth()){ switch(keyCode){ case LEFT: case UP: case DOWN: case END: moveCaretLeft(endTLHI); validKeyCombo = true; break; case RIGHT: if(!moveCaretRight(endTLHI)) moveCaretLeft(endTLHI); validKeyCombo = true; } // Calculate new caret position // calculateCaretPos(startTLHI); calculateCaretPos(endTLHI); } //**************************************************************** calculateCaretPos(endTLHI); if(!shiftDown) startTLHI.copyFrom(endTLHI); bufferInvalid = true; } } protected void keyTypedProcess(int keyCode, char keyChar, boolean shiftDown, boolean ctrlDown){ int ascii = (int)keyChar; newline = false; backspace = false; if(isDisplayable(ascii)){ if(hasSelection()) stext.deleteCharacters(pos, nbr); stext.insertCharacters("" + keyChar, pos); adjust = 1; textChanged = true; } else if(keyChar == BACKSPACE){ if(hasSelection()){ stext.deleteCharacters(pos, nbr); adjust = 0; textChanged = true; } else if(stext.deleteCharacters(pos - 1, 1)){ adjust = -1; textChanged = true; backspace = true; } } else if(keyChar == DELETE){ if(hasSelection()){ stext.deleteCharacters(pos, nbr); adjust = 0; textChanged = true; } else if(stext.deleteCharacters(pos, 1)){ adjust = 0; textChanged = true; } } else if(keyChar == ENTER || keyChar == RETURN) { if(stext.insertEOL(pos)){ adjust = 1; textChanged = true; newline = true; } } else if(keyChar == TAB){ // If possible move to next text control if(tabManager != null){ boolean result = (shiftDown) ? tabManager.prevControl(this) : tabManager.nextControl(this); if(result){ startTLHI.copyFrom(endTLHI); return; } } } // If we have emptied the text then recreate a one character string (space) if(stext.length() == 0){ stext.insertCharacters(" ", 0); adjust++; textChanged = true; } } protected boolean changeText(){ if(!super.changeText()) return false; // The following actions handle multi-line stuff // Do we have to move cursor to start of next line if(newline) { if(pos >= stext.length()){ stext.insertCharacters(" ", pos); stext.getLines(buffer.g2); } moveCaretRight(endTLHI); calculateCaretPos(endTLHI); } if(backspace && pos > 0){ char ch = stext.getPlainText().charAt(pos-1); if(ch == '\n'){ moveCaretRight(endTLHI); calculateCaretPos(endTLHI); } if(pos >= stext.length()){ stext.insertCharacters(" ", pos); stext.getLines(buffer.g2); } } // Finish off by ensuring no selection, invalidate buffer etc. startTLHI.copyFrom(endTLHI); return true; } /** * Move caret to home position * @return true if caret moved else false */ protected boolean moveCaretStartOfLine(TextLayoutHitInfo currPos){ if(currPos.thi.getCharIndex() == 0) return false; // already at start of line currPos.thi = currPos.tli.layout.getNextLeftHit(1); return true; } protected boolean moveCaretEndOfLine(TextLayoutHitInfo currPos){ if(currPos.thi.getCharIndex() == currPos.tli.nbrChars - 1) return false; // already at end of line currPos.thi = currPos.tli.layout.getNextRightHit(currPos.tli.nbrChars - 1); return true; } protected boolean moveCaretStartOfText(TextLayoutHitInfo currPos){ if(currPos.tli.lineNo == 0 && currPos.thi.getCharIndex() == 0) return false; // already at start of text currPos.tli = stext.getTLIforLineNo(0); currPos.thi = currPos.tli.layout.getNextLeftHit(1); return true; } protected boolean moveCaretEndOfText(TextLayoutHitInfo currPos){ if(currPos.tli.lineNo == stext.getNbrLines() - 1 && currPos.thi.getCharIndex() == currPos.tli.nbrChars - 1) return false; // already at end of text currPos.tli = stext.getTLIforLineNo(stext.getNbrLines() - 1); currPos.thi = currPos.tli.layout.getNextRightHit(currPos.tli.nbrChars - 1); return true; } protected boolean moveCaretUp(TextLayoutHitInfo currPos){ if(currPos.tli.lineNo == 0) return false; TextLayoutInfo ntli = stext.getTLIforLineNo(currPos.tli.lineNo - 1); TextHitInfo nthi = ntli.layout.hitTestChar(caretX, 0); currPos.tli = ntli; currPos.thi = nthi; return true; } protected boolean moveCaretDown(TextLayoutHitInfo currPos){ if(currPos.tli.lineNo == stext.getNbrLines() - 1) return false; TextLayoutInfo ntli = stext.getTLIforLineNo(currPos.tli.lineNo + 1); TextHitInfo nthi = ntli.layout.hitTestChar(caretX, 0); currPos.tli = ntli; currPos.thi = nthi; return true; } /** * Move caret left by one character. If necessary move to the end of the line above * @return true if caret was moved else false */ protected boolean moveCaretLeft(TextLayoutHitInfo currPos){ TextLayoutInfo ntli; TextHitInfo nthi = currPos.tli.layout.getNextLeftHit(currPos.thi); if(nthi == null){ // Move the caret to the end of the previous line if(currPos.tli.lineNo == 0) // Can't goto previous line because this is the first line return false; else { // Move to end of previous line ntli = stext.getTLIforLineNo(currPos.tli.lineNo - 1); nthi = ntli.layout.getNextRightHit(ntli.nbrChars-1); currPos.tli = ntli; currPos.thi = nthi; } } else { // Move the caret to the left of current position currPos.thi = nthi; } return true; } /** * Move caret right by one character. If necessary move to the start of the next line * @return true if caret was moved else false */ protected boolean moveCaretRight(TextLayoutHitInfo currPos){ TextLayoutInfo ntli; TextHitInfo nthi = currPos.tli.layout.getNextRightHit(currPos.thi); if(nthi == null){ // Move the caret to the start of the next line the previous line if(currPos.tli.lineNo >= stext.getNbrLines() - 1) // Can't goto next line because this is the last line return false; else { // Move to start of next line ntli = stext.getTLIforLineNo(currPos.tli.lineNo + 1); nthi = ntli.layout.getNextLeftHit(1); currPos.tli = ntli; currPos.thi = nthi; } } else { // Move the caret to the right of current position currPos.thi = nthi; } return true; } /** * Move the insertion point (caret) to the specified line and character. If the position is invalid * then the caret is not moved. The text will be scrolled so that the caret position is visible. * * @param lineNo the line number (starts at 0) * @param charNo the character position on the line (starts at 0) */ public void moveCaretTo(int lineNo, int charNo){ try { TextLayoutHitInfo tlhi = stext.getTLHIforCharPosition(lineNo, charNo); if(tlhi != null){ startTLHI.copyFrom(tlhi); endTLHI.copyFrom(tlhi); calculateCaretPos(tlhi); keepCursorInView = true; showCaret = true; } } catch(Exception e){} } /** * Get the current caret position. <br> * * The method will always return a 2 element array with the current caret position * { line no, char no } <br> * * If the current caret position is undefined then it will return the array { -1, -1 } * * @return a two element int array holding the caret position. */ public int[] getCaretPos(){ return getCaretPos(null); } /** * Get the current caret position. <br> * * If the parameter is a 2 element int array then it will be populated with the line number [0] * and character no [1] of the caret's current position. <br> * * The method will always return a 2 element array with the current caret position * { line no, char no } <br> * * If the current caret position is undefined then it will return the array { -1, -1 } * * @param cpos array to be populated with caret position * @return a two element int array holding the caret position. */ public int[] getCaretPos(int [] cpos){ if(cpos == null || cpos.length != 2) cpos = new int[2]; if(endTLHI == null || endTLHI.tli == null || endTLHI.thi == null){ cpos[0] = cpos[1] = -1; } else { cpos[0] = endTLHI.tli.lineNo; cpos[1] = endTLHI.thi.getCharIndex(); } return cpos; } /** * Will respond to mouse events. */ public void mouseEvent(MouseEvent event){ if(!visible || !enabled || !available) return; calcTransformedOrigin(winApp.getCursorX(), winApp.getCursorY()); ox -= tx; oy -= ty; // Remove translation currSpot = whichHotSpot(ox, oy); if(currSpot == 1 || focusIsWith == this) cursorIsOver = this; else if(cursorIsOver == this) cursorIsOver = null; switch(event.getAction()){ case MouseEvent.PRESS: if(currSpot == 1){ if(focusIsWith != this && z >= focusObjectZ()){ keepCursorInView = true; takeFocus(); } dragging = false; if(stext == null || stext.length() == 0){ stext = new StyledString(" ", wrapWidth); stext.getLines(buffer.g2); } endTLHI = stext.calculateFromXY(buffer.g2, ox + ptx, oy + pty); startTLHI = new TextLayoutHitInfo(endTLHI); calculateCaretPos(endTLHI); bufferInvalid = true; } else { // Not over this control so if we have focus loose it if(focusIsWith == this) loseFocus(null); } break; case MouseEvent.RELEASE: dragging = false; bufferInvalid = true; break; case MouseEvent.DRAG: if(focusIsWith == this){ keepCursorInView = true; dragging = true; endTLHI = stext.calculateFromXY(buffer.g2, ox + ptx, oy + pty); calculateCaretPos(endTLHI); fireEvent(this, GEvent.SELECTION_CHANGED); bufferInvalid = true; } break; } } protected void calculateCaretPos(TextLayoutHitInfo tlhi){ float temp[] = tlhi.tli.layout.getCaretInfo(tlhi.thi); caretX = temp[0]; caretY = tlhi.tli.yPosInPara; } }