/************************************************************************** * Copyright (c) 2001, 2002, 2003 by Acunia N.V. All rights reserved. * * * * This software is copyrighted by and is the sole property of Acunia N.V. * * and its licensors, if any. All rights, title, ownership, or other * * interests in the software remain the property of Acunia N.V. and its * * licensors, if any. * * * * This software may only be used in accordance with the corresponding * * license agreement. Any unauthorized use, duplication, transmission, * * distribution or disclosure of this software is expressly forbidden. * * * * This Copyright notice may not be removed or modified without prior * * written consent of Acunia N.V. * * * * Acunia N.V. reserves the right to modify this software without notice. * * * * Acunia N.V. * * Philips-site 5, box 3 info@acunia.com * * 3001 Leuven http://www.acunia.com * * Belgium - EUROPE * **************************************************************************/ package com.acunia.wonka.rudolph.peers; import java.awt.*; /* ** TextArea Painter for line-wrapped texts, simplification for fixed-width fonts ** => Programming: ** - As we the number of lines varies with the screen width, textlineEnd[] wil no longer be a fixed value of the text. ** Instead we store the text as a whole, including the '\n' endline characters and recalculating textlineEnd[] along ** with the visible parts ** - rewritten the two setScreenPosition functions to take into account the new storage method ** - with viewport.width in characters, calculateVisibleLineparts can just take the number of chars in the word for the words width, ** the space width becomes 1(char) */ public class TextAreaPainter_WrappingFixFont extends TextAreaPainter { protected int painterCharWidth; // = fm.getMaxAdvance for fixed width characters, -1 for variable width /* ** Constructor */ public TextAreaPainter_WrappingFixFont(Font textfont, FontMetrics metrics, int fixedwidth, String text, Dimension size, Color[] colors) { super(metrics,size,colors); viewport.width/=fixedwidth; // fixed width is calculated in chars instead of pixels painterFont = textfont; //RudolphPeer.DEFAULT_FONT; painterMetrics = metrics; painterCharWidth = fixedwidth; //text and text arrays setImage(text); } /* ** Special data access functions: since all widths are equal, we can calculate the horizontal lines in chars instead of pixels */ public int getMaxAdvance() { // we always advance 1 char per click (we calculate in chars now, didn't I tell yet? return 1; } /* ** new screen size in chars and text lines instead of pixels and lines */ public void setSize(Dimension newsize) { //width and height in characters viewport.width = (RudolphTextAreaPeer.getInnerwidth(newsize.width))/painterCharWidth; //the effective width in chars viewport.height = RudolphTextAreaPeer.getLines(newsize.height,painterMetrics); //also calculate the nwe visible line parts calculateVisibleLineparts(); } /* ** Store the new text in buffer. Store the full text including the '/n' endline marks and call calculateVisibleLineparts */ public void setImage(String text) { //initialise text buffer and fill with complete text textBuffer = new char[text.length()];// we include the breaks for easyer line calculation text.getChars(0,textBuffer.length,textBuffer,0); //calculate the lines if(viewport.width>0) { //build new visible text arrays calculateVisibleLineparts(); } else { //by default set end buffer to int[0] so getLines returns a value textlineEnd = new int[0]; } } /* ** calculate lines, startChars and StopChars for given text, and viewport ** with viewport.width in characters, the words width, is simply the number of characters in that word, the space width becomes 1(char) ** Note that the actual number of pieces will be bigger then the number of text lines */ protected void calculateVisibleLineparts() { //security if(textBuffer.length<=0 || viewport.width<=0) { textlineEnd = new int[0]; viewTextOffset = new int[0]; viewTextLength = new int[0]; return; } // ALGORITHM: // For simplicity, we use a tree-step algorithm: // STEP ONE: we break the text into words, storing each word in a temporary array. As we're using a char[] buffer and start-stop-position // arrays, we'll 'store' the words as a series of 3 arrays: int wordstart, int wordlength and boolean newline // STEP TWO: we'll re-calculate the length of line through its subsequent word length, splitting the line in the middle every time the // length exceeds the viewport length (splitting is simply done by inserting a new 'true' in the newline buffer) // STEP THREE: we assing a new pair of viewTextOffset/viewTextLength arrays and fill them with the lines // STEP ONE: // calculate number of words == number of space characters + number of lines int i; //we'll use the for(int i==.. ;i<... ;i++) for-next loop so often, we can just use a common i int words=1; for(i=0;i<textBuffer.length; i++) { if(textBuffer[i]==' ' || textBuffer[i]=='\n'|| textBuffer[i]=='.' || textBuffer[i]==',' || textBuffer[i]=='?' || textBuffer[i]==':' || textBuffer[i]==';') { words++; } } // build arrays for word start, stop and newline int[] wordstart = new int[words]; int[] wordstop = new int[words]; boolean[] newline = new boolean[words]; // fill arrays with words int line=0; //first line (length will be calculated out of next line start) wordstart[0]=0; newline[0]=true; //all lines in between for(i=1; i<(textBuffer.length-1); i++) { if(textBuffer[i]=='\n' || textBuffer[i]==' ' || textBuffer[i]=='.' || textBuffer[i]==',' || textBuffer[i]=='?' || textBuffer[i]==':' || textBuffer[i]==';') { if(textBuffer[i]=='\n' || textBuffer[i]==' ' ) { wordstop[line]=i; } else { wordstop[line]=i+1; } line++; wordstart[line]=i+1; //skip the /n-character newline[line] = (textBuffer[i]=='\n'); //true if end of line, false otherwise } } wordstop[line]= textBuffer.length; //STEP TWO: // add <newline=true; > break commands whenever the width of the chars up to now exceeds the viewport width //first simplification: A word longer then the current viewport will always start on a new line and occupy the whole line //so the word after them has to start on a new line just as well int last = words-1; for(i=0; i<last; i++) { if((wordstop[i]-wordstart[i])>=viewport.width) { newline[i]=true; wordstop[i]= wordstart[i]+viewport.width; newline[i+1]=true; } } //last line if((wordstop[last]-wordstart[last])>=viewport.width) { newline[last]=true; wordstop[last]= wordstart[last]+viewport.width; } // insert a new line flag every time the current length doesn't fit on one line line=1; int linestop= viewport.width; for(i=1; i<words; i++) { if(newline[i] || wordstop[i]>=linestop) { // either new line or too large for viewport: newline[i]=true; linestop=wordstart[i]+viewport.width; line++; } } //STEP THREE: // Copy the line start and stop into the viewTextOffset[]/viewTextLength[] buffers textlineEnd = new int[line]; viewTextOffset = new int[line]; viewTextLength = new int[line]; line=0; viewTextOffset[0]=wordstart[0]; for(i=1; i<words; i++) { if(newline[i]) { textlineEnd[line]=wordstop[i-1]; viewTextLength[line]=wordstop[i-1]-viewTextOffset[line]; line++; viewTextOffset[line]=wordstart[i]; } } //last line, skip ending /n textlineEnd[line]=(textBuffer[textBuffer.length-1]=='\n')?textBuffer.length-1:textBuffer.length; viewTextLength[line]=textlineEnd[line]-viewTextOffset[line]; } /* ** screen position to position in displayed text and vice versa As we include the line-breaks in the buffer, there is no difference ** between buffer position and TextAreaPainter text string position */ public void setScreenPosition(int x, int y, int[] pointdata) { //security if(viewport.width<=0 || viewport.height<=0 ||textlineEnd.length<=0 ) { return; } // no of lines on screen, with offset pointdata[1] = RudolphTextAreaPeer.getLine(y, painterMetrics) + lineOffset; if(pointdata[1]>=textlineEnd.length) { // deeper then last line pointdata[1]= textlineEnd.length-1; } // x-position of the line in in chars pointdata[2] = RudolphTextAreaPeer.getTextPos(x)/painterCharWidth; if(pointdata[2]>viewTextLength[pointdata[1]]) { // we clicked past the end of the current line => adjust to that line pointdata[2]=viewTextLength[pointdata[1]]; } // text position pointdata[3] = viewTextOffset[pointdata[1]] + pointdata[2]; // as the wrapping text buffer DOES include \n line breaks, the screen text position IS the buffer position pointdata[0] = pointdata[3]; } /* ** screen position to position in displayed text */ public void setScreenPosition(int pos, int[] pointdata) { //System.out.println( "FixedWrappingTAPainter: setScreenPosition : finding data for TextPoint pos "+pos); if(viewport.width<=0 || viewport.height<=0 ||textlineEnd.length<=0 ) { return; } //security if(pos<0) { //no selection pointdata[0] = 0; pointdata[1] = 0; pointdata[2] = 0; pointdata[3] = 0; } else if( pos >=textBuffer.length) { //passed the end of buffer pointdata[0] = textBuffer.length; pointdata[1] = textlineEnd.length-1; pointdata[2] = viewTextLength[pointdata[1]]; pointdata[3] = textBuffer.length; } else { //Ok, in range pointdata[0] = pos; //calculate line out of textlineEnd[] pointdata[1]=0; while(pos>textlineEnd[pointdata[1]]) { pointdata[1]++; } pointdata[2] = pos-viewTextOffset[pointdata[1]]; // the wrapping text buffer also includes end lines, so the textbuffer position is the text string position pointdata[3] = pos; } //System.out.println( " => found line:"+pointdata[1]+ " Char:"+pointdata[3]+", offset:"+pointdata[2]); } /* ** char position in line, offset to pointdata : ** pointData[1]: line and pointData[2] offset(in chars) are given. ** Up to us to calculate the corresponding text position and buffer position ** special case: when offset== -1 calculate the end of the line */ public void setScreenPositionLine(int line, int offset, int[] pointdata) { if(line<0) { // set all to beginning of text (first line, first pos) pointdata[0]=0; pointdata[1]=0; pointdata[2]=0; pointdata[3]=0; } else if(line>=textlineEnd.length) { // set all to end of text pointdata[0]=textBuffer.length; pointdata[1]=textlineEnd.length-1; pointdata[2]=viewTextLength[pointdata[1]]; pointdata[3]=textBuffer.length; } else if(offset<0) { pointdata[0]=textlineEnd[line]; pointdata[1]=line; pointdata[2]=viewTextLength[line]; pointdata[3]=textlineEnd[line]; } else { pointdata[1]=line; if(offset>viewTextLength[line]) { pointdata[2]=viewTextLength[line]; pointdata[0]=textlineEnd[line]; } else { pointdata[0]=(line>0)? textlineEnd[line-1]+offset+1: offset; pointdata[2]=offset; } pointdata[3]=pointdata[0]; } } /* ** new paint for selections: Cursor and selection offset are given in chars, we have to calculate them back to pixels */ public void paint(int width, int height, int cursorline, int cursoroffset, int cursorscreenpos, Graphics g) { RudolphTextAreaPeer.paintTextArea( 0,0,width,height, textBuffer,viewTextOffset,viewTextLength,lineOffset, cursorline, cursorscreenpos*painterCharWidth, painterFont, painterMetrics, textColors, g, drawCursor); } public void paint(int width, int height, int startline, int startbufferoffset, int starthorizontaloffset, int stopline, int stopbufferoffset, int stophorizontaloffset,Graphics g) { RudolphTextAreaPeer.paintTextArea( 0,0,width,height, textBuffer,viewTextOffset,viewTextLength,lineOffset, startline, startbufferoffset, starthorizontaloffset * painterCharWidth, stopline, stopbufferoffset, stophorizontaloffset * painterCharWidth, painterFont, painterMetrics, textColors, g, drawCursor); } }