text editor too slow
Hi,
I'm trying to create a text editor applet in Java 1.1.8.
My way I go is:
Every time a key is pressed, the character is added to an array and repaint is called. The pain method displays the text is displayed on a canvas by going through the array and calling drawString with the calculated x- and y-position.
This works basically, but when the text input is fast, the display method is too slow, which means that the editor panel begins flickering.
Does anybody know a better way to do this?
Thanks in advance
Peter
Just because the editor flickers doesn't mean it's not keeping up with the typing.
The way to prevent flickering is to override the update() method to call paint() directly.
public void update(Graphics g) { paint(g); }
I have done this. Perhaps flickering is not the right word to describe what happens. When I type quite fast, the new text does not appear immediately, but after a while.I think that the paint method is called when not finished with the first operation.
Interesting. I am going to do a simular project myself...(havent realy started yet)
Got an idea for you:
If you use an offscreen image, and a new character is typed, you could paint only that character to the offscreenimage, and then perform a repaint().
If I understood you right you are doing like this:
* clear the image.
* paint every single char that was allready there to the image
* paint the new char to the image.
* paint the image on the Component.
This is my alternative:
* do not clear the image (saving time)
* do not paint the old chars (they were never cleard, so they are allready there)
* paint the new char (thats fast!)
* paint the image to the component.
What do you think? Deletion and related stuff gets more complicated with my way... But it should be possible to solve as well. Keep me updated, I would be glad to have a dialog about this.
rbb@rubicon.no
Regards,
";-)
Ragnvald Barth
Software engineer
http://blake.prohosting.com/ragnvald
Im working on a textfield too and have the same problem. Although my textfield becomes too slow with word wrapping and when the input is very long single word. The answer could be to paint only the input character, not the whole content, but if caret is int the middle of the input text, or in worst case, at the beginning, then the you have to paint the whole content on every keypress.
Here's my paint code that implements word wrapping. Problem is in the inner while-loop, that iterates a string that is too long for the textfield width by dropping characters one at time from the end of the string, so that eventually the substring fits the area. This is very slow with a long strings.
If you get any ideas for implementing this good way, please let me know!
FontMetrics fm = og.getFontMetrics();
StringTokenizer st = new StringTokenizer(currentParagraph, " .,!?:;", true);
int text_x = 0;
int text_y = fm.getHeight();
int lineWidth = 0;
while (st.hasMoreTokens())
{
String word = st.nextToken();
lineWidth = lineWidth + fm.stringWidth(word);
if (lineWidth >= this.getSize().width)
{
text_y = text_y + fm.getHeight();
text_x = 0;
lineWidth = fm.stringWidth(word);
int start = 0;
int end = word.length();
while (lineWidth >= this.getSize().width)
{
String tmpWord = word.substring(start, end);
int tmpLineWidth = fm.stringWidth(tmpWord);
while (tmpLineWidth >= this.getSize().width)
{
end--;
tmpWord = word.substring(start,end);
tmpLineWidth = fm.stringWidth(tmpWord);
}
og.drawString(tmpWord, text_x, text_y);
text_y = text_y + fm.getHeight();
start = end;
if (start < word.length())
{
word = word.substring(start, word.length());
}
else
word = "";
lineWidth = fm.stringWidth(word);
start = 0;
end = word.length();
}
}
og.drawString(word, text_x, text_y);
text_x = lineWidth;
}
og.drawLine(lineWidth, text_y, lineWidth, text_y-16);
Problem is in the inner while-loop, that iterates a string that is too long for the textfield width by dropping characters one at time from the end of the string, so that eventually the substring fits the area. This is very slow with a long strings.
That problem shouldn't be too hard to solve. Why do you drop the characters one at a time? I can think of two other ways to drop the characters. One is the binary way. You are searching for a speciffic point between start and end. Perform a binary search! something like this (this code is not compiled or tested):
int iThisWidth = getSize().width;//only one call to getSize().width is needed in paint()!
int end1 = start;
int end2 = end;
while(end1 < end2 +1)
{
tmpWord = word.substring(end1, end2);
tmpLineWidth = fm.stringWidth(tmpWord);
if(tmpLineWidth < iThisWidth)
end1 += (end2-end1)/2;
else
end2 -= (end2-end1)/2;
}
end = end2;
My second idea is to take a guess on the length of the string. The word has width lineWidth (withc is too big), and you want to cut it to be getSize().width or smaller.
If you cut off (lineWidth/getSize().width) it becomes perfect. If each character has the same with, (witch is close enough to the thruth considdering we are working with guessing) the number of characters to use would be:
lineWidth/getSize().width*word.length().
Guessing code (not compiled or debugged):
int iGuess;
int iThisWidth = getSize().width
iGuess = (int)(((double)lineWidth / iThisWidth)*word.length());
// now test if iGuess is ok. if not adjust it.
One more thing to say: What about remembering the width of each letter so that you wouldnt need to calculate it every time you paint?
class Letter{
private char ch;
private int iWidth;
public int getWidth()
{ return iWidth; }//fast!
}
Then you could get the width using faster methods than the ones provided by fontMetrics. And substring is realy slow! (because it creates new String()). Avoid createing new Strings all the time. Use a Letter class and an array of Letter's, with indexes pointing in to it or something...
Good luck!
Ragnvald
Thank you for your help, I appreciate it very much!
I made some tests with binary search, and it was much faster but not fast enough (in situations where word is so long that it takes many rows, maybe the whole text area).
I haven't tested to store each letter's width, but I think there would be similar problem, that is with a very long word there would be a loop that takes long time to run.
If you (or anyone reading this) come up with some ideas, please post them here.
regards,
dr sykero
To increase performance you have two options:
1) Make as few loops as possible in the inner loop.
2) Make the inner loop fast.
You are doing the following two calls:String.substring(int, int);
FontMetrics.StringWidth(String);
You have to make a test to measure them. I beleeve they are both quite slow, especially StringWidth() !!!
Don't underestimate binary search! (It's O log(n)performance). If you have 1'000'000 elements you will in worst case use 20 iterations to find what you are seeking using binary search.
I beleeve soring the width will solve your problem:
But don't only store each letters with. Store each letters offset from the left border as well. Because then you can make a superfast StringWidth function.
Ragnvald
> One more thing to say: What about remembering the
> width of each letter so that you wouldnt need to
> calculate it every time you paint?
> > class Letter{
> private char ch;
> private int iWidth;
> public int getWidth()
> { return iWidth; }//fast!
> }
>
> Then you could get the width using faster methods than
> the ones provided by fontMetrics. And substring is
> realy slow! (because it creates new String()). Avoid
> createing new Strings all the time. Use a Letter class
> and an array of Letter's, with indexes pointing in to
> it or something...
Generally I think this is a good idea. In the letter class any font information could be saved like
font size (character height), font family, font style. But this could give raise to a memory problem.
Recently I got an out of memory error when I created an offscreen image
100 * getSize().width * getSize().height. It seems that the java interpreter is restricted in memory usage.
> It seems that the java interpreter is restricted in memory usage. IIRC, the defaul maximum heap size is 64 megs but you can increase it using command line options.
But what can I do with an applet?
You cant use an applet to edit realy huge documents. Then you would get an out of memory error no matter what how you do it, because you cant get away from storing each char. That gives you a size of two bytes per letter. Then, if you also make a Letter class out of it, and store each Letters width, you get six bytes per letter. Thats realy not going to be a problem unless the document is increadably huge.
And maybee you end up with more than six bytes.
For an advanced text- editor I would end up having something like this:
Color (reference 4 bytes)
Font(reference 4 bytes)
width(int 4 bytes)(-or use byte for storing this value)
height(int 4 bytes)(-or use byte for storing this value)
Image(inserting an image in the text could be cool. Reference 4 bytes, usually null)
total 20 bytes per letter. That gives you a size limit ten times smaller than using just a char. But is this limitation realy a problem?
How huge document's do you need to support?
Do you need a realy thyny letter like this?
class Letter
{
private byte width;
private char ch;
public int getWidth()
{
return (int)width;
}
}
My guess is no. It's better to make limitations on the size of the document. After all, this is an applet, and its not going to be used to edit the new edition of the complete Britannica.
I need to remember just width for each letter (and character of course). Purpose of all this is to make XLet (applet in DigiTV) that you can use to write and read mail. So I don't need to change font or color within a document.
I was wondering how much it takes to store a reference to an object, sizeof int or long perhaps?
That is, if I have an object and two variables pointing to that single object, the information wrapped in object is stored only once, right?
This means that I could store all the information about letters only once, and my document would be stored in an array of pointers to those letters.
Yes you are right. A reference is 4 bytes.