Use of JTextPane in Chatting Application

S

Sameer

A JTextPane is a text component that can be marked up with
attributes that are represented graphically.

In the process of writing a chatting application, i am going to use
JTextPane for accepting the text from user on a client (So that
underline can also be displayed which can not be displayed using
JTextField).

Can i get different format for each character (This feature is not
available in Yahoo Messenger) ?
I want to send this information to another machine.
How to pack all the formatting of the text with different attribute of
each character in a single class so that i can send this object by
serializing it? Is it possible that JTextPane can be represented by a
single class with all the formatting information available with it?
What are the related 'returning' methods of JTextPane

Any idea about implementing Emoticons (Emotion Icons)? JTextPane let us
insert icons, but can we wrap this information in a class to transfer
it over the socket?
Please help me if you have any idea about it.
-Sameer
 
A

Andrew Thompson

What are the related 'returning' methods of JTextPane..

You can probably ascertain that by examining the JavaDocs.
<http://java.sun.com/j2se/1.5.0/docs/api/javax/swing/JTextPane.html>
...or would you like me to read them out to you?

Note: X-Post
comp.lang.java.programmer,
comp.lang.java.gui,
comp.lang.java.help

Please do not cross posst to more than two groups, and direct
Follow-Ups to a *single* group. It is usually not a good idea
to X-Post between c.l.j.help and any other Java group.

Follow-Ups to *this* post set to c.l.j.gui only.
 
R

Roedy Green

Can i get different format for each character

You need some code like this:

// window where other people's chat appears
JEditorPane output;

output = new JEditorPane();
output.setContentType( "text/html" );
output.setEditable( false );
output.setBackground( lemonChiffon );
// Serif has best chance of supporting Unicode.
output.setFont( new Font( "Serif", Font.PLAIN, 13 ) );
output.setForeground( black );

Now you fill the pane with primitive html to get your colours, e.g.

output.setText( "<html><body><font
color="red">cherry</font></body></html>");


See http://mindprod.com/jgloss/htmcheat.html
for a crash course in HTML.
 
R

Roedy Green

Can i get different format for each character (This feature is not
available in Yahoo Messenger) ?
I want to send this information to another machine.
How to pack all the formatting of the text with different attribute of
each character in a single class so that i can send this object by
serializing it? Is it possible that JTextPane can be represented by a
single class with all the formatting information available with it?
What are the related 'returning' methods of JTextPane

I am not sure of this but I think internally JTextField (and its child
JTextPane) uses either RTF or HTML internally, whichever you set with
setContentType. I would expect style changes you make either by
feeding it HTML or by using the style setting commands would be
reflected in any of the getText methods. You can find out pretty
quickly with a simple experiment. Do a getText and see what it gives
you.
 
R

Roedy Green

Any idea about implementing Emoticons (Emotion Icons)? JTextPane let us
insert icons, but can we wrap this information in a class to transfer
it over the socket?

JTextPanes so far as I know do not display embedded images. So the
only way you could get emoticons is as a font.

You need a free font of emoticons your users can install independently
of Java, or you need to use the relatively recent tools for bundling a
font in a resource file. For frequent players, you would get better
speed by intalling the font once and for all than downloading it each
game play as an Applet resource.

You have to find a free font with emoticons.

to start your search see http://mindprod.com/jgloss/font.html

There is another approach, probably too slow, generating HTML and
handing it off to the browser to render with showDocument. Then you
could display anything, even hyperlinks.

There is yet another approach, the hairy chested approach where you
render everything yourself on a Canvas. Have a look at
http://mindprod.com/projects/javapresenter.html

What I have effectively done is create tokenized form of HTML that I
render myself with colours and fonts. This is very fast. I could
potentially display images too.

Here is the key class. Most of the code is concerned with rendering no
more than the narrow band requested, and rapidly finding the data for
that band to render. The lines of text are not evenly spaced
vertically. That is why it is so complicated.


package com.mindprod.jdisplay;

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.util.Arrays;

import com.mindprod.jtokens.NL;
import com.mindprod.jtokens.Token;

/**
* Renders a string of tokens, usually representing Java source code.
Does not
* handle its own scrolling.
*
* @author Roedy Green
* @version 1.0
*/
public class PrettyCanvas extends Canvas {

/**
* true if want extra debug output
*/
private static final boolean DEBUGGING = false;

/**
* has the accelerator to render only necessary tokens kicked in
yet?
*/
private boolean accelerated = false;

/**
* counts how many bands we have. If there were no blank lines,
would be
* same as number of lines. Normally the value is a little less
that the
* number of lines since a strip of vertical white space counts as
one
* bandCount.
*/
private int bandCount;

/**
* baseline in pixels down from top of canvas that bandCount
renders on.
* indexed by bandCount. There will be one entry per non-blank
line here.
*/
private int[] baselines;

/**
* Dimensions of the scrollable footprint. Start with dummy in
case we get
* queried before set called.
*/
private Dimension dimension = new Dimension( 10, 10 );

/**
* first line number to render in a given band.
*/
private int[] firstLineNumbersInBand;

/**
* first token to render in a given band.
*/
private int[] firstTokensInBand;

/**
* true if want lineNumbers
*/
private boolean hasLineNumbers;

/**
* how many pixels wide line numbers are
*/
private int lineNumberWidth;

/**
* top most baseline where we start rendering a bandCount.
*/
private int startAtBaseline;

/**
* 1-based line number to start rendering the current bandCount.
*/
private int startAtLineNumber;

/**
* Array of tokens to render
*/
private Token[] tokens;

/**
* Total lines of text in the entire array of Tokens, which is
considerably
* smaller than the total number of tokens.
*/
private int totalLines;

/**
* accelerate rendering by computing just which tokens need to be
rendered
* for a given bandCount. Get the index of the first Token
*
* @param r
* clip region to be rendered.
* @return first token index that needs to be rendered.
*/
private int firstTokenNeedToRender ( Rectangle r )
{
int topOfBand = r.y;
// pick a baseline just prior to the band for safety.
int firstBaseline = topOfBand - Geometry.LEADING_IN_PIXELS;
// home in on a unique 0-based band
int band = Arrays.binarySearch( baselines, firstBaseline );
if ( band < 0 )
{
// convert insertion point to the band below.
int insert = -band - 1;
band = insert - 1;
band = Math.min( Math.max( 0, band ), bandCount - 1 );
}
// As side benefit, we get the startAtBaseline and
starAtLineNumber
startAtBaseline = baselines[ band ];
// startAtLineNumber is 1-based.
startAtLineNumber = firstLineNumbersInBand[ band ];
return firstTokensInBand[ band ];
}

/**
* accelerate rendering by computing just which tokens need to be
rendered
* for a given bandCount.
*
* @param r
* clip region to be rendered.
* @return last token index that needs to be rendered.
*/
private int lastTokenNeedToRender ( Rectangle r )
{
int bottomOfBand = r.y + r.height; /* y increases down the
screen */
// pick a baseline just after the band for safety.
// With multiple nls it could be part way through the band,
but with
// nothing
// to render after it.
int lastBaseline = bottomOfBand + Geometry.LEADING_IN_PIXELS;

// home in on a unique 0-based band
int band = Arrays.binarySearch( baselines, lastBaseline );
if ( band < 0 )
{
int insert = -band - 1;
// with an inexact hit we want the conservative choice the
band
// after
// the insert point.
band = insert;
band = Math.min( Math.max( 0, band ), bandCount - 1 );
}
if ( band == bandCount - 1 )
{
// we are on the last band, render even the very last
token.
return tokens.length - 1;
}
else
{
/*
* the last token of the band is the token just before the
token on
* the start of the next band.
*/
return firstTokensInBand[ band + 1 ] - 1;
}
}

/**
* Record where on page we started rendering a given band i.e.
line with
* text on it.
*
* @param baseline
* y in of baseline in pixels from the top of canvas.
* @param lineNumber
* one-based line number being rendered
* @param tokenIndex
* index of first token on the line, including possibly NL
though
* normally it would be the last token of the previous
line.
*/
private void lineRenderedAt ( int baseline, int lineNumber, int
tokenIndex )
{

baselines[ bandCount ] = baseline;
firstTokensInBand[ bandCount ] = tokenIndex;
firstLineNumbersInBand[ bandCount ] = lineNumber;
bandCount++ ;
}

/**
* Clear binary search arrays used to accelerate rendering by
finding only
* those tokens we need to render.
*/
private void prepareAccelerator1 ()
{
if ( tokens == null || tokens.length == 0 ) { return; }
// these are a little bigger than we need.
bandCount = 0;
baselines = new int[ totalLines ];
firstTokensInBand = new int[ totalLines ];
firstLineNumbersInBand = new int[ totalLines ];
// debugging, fill with easy to spot invalid values.
for ( int i = 0; i < totalLines; i++ )
{
baselines[ i ] = -10;
firstTokensInBand[ i ] = -20;
firstLineNumbersInBand[ i ] = -30;
}
}

/**
* Prepare to use the accelerator by trimming its arrays back to
perfect
* size. We have collected data on where each band is rendering.
*/
private void prepareAccelerator2 ()
{
// trim arrays back precisely to size so that binary search
will work.
int[] old = baselines;
baselines = new int[ bandCount ];
System.arraycopy( old, 0, baselines, 0, bandCount );

old = firstTokensInBand;
firstTokensInBand = new int[ bandCount ];
System.arraycopy( old, 0, firstTokensInBand, 0, bandCount );

old = firstLineNumbersInBand;
firstLineNumbersInBand = new int[ bandCount ];
System.arraycopy( old, 0, firstLineNumbersInBand, 0, bandCount
);

}

/**
* called whenever system has a slice to render
*
* @param g
* Graphics defining where and region to paint.
*/
public void paint ( Graphics g )
{
// No need to call super.paint(); update has already cleared
the screen.
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
g2d.setRenderingHint( RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY );
// if wanted to smooth geometric shapes too
// g2d.setRenderingHint( RenderingHints.KEY_ANTIALIASING,
// RenderingHints.VALUE_ANTIALIAS_ON );

render( g2d );
}

/**
* does drawing. simiar to logic in Footprint.calcPayloadFootprint
*
* @param g
* where to paint
*/
public void render ( Graphics2D g )
{
// We avoid rendering before or after the clip region.
// Normally we only render a 4 pixel high band at a time.
Rectangle r = g.getClipBounds();

// No need to clear the background of just the clip region
// because update handles.
// g.setColor( this.getBackground() );
// g.fillRect ( r.x, r.y, r.width, r.height );

// render all the tokens, some may be offscreen, but no
matter.
if ( tokens == null || tokens.length == 0 ) { return; }

// use rendering hints

// locals
boolean firstTokenOnLine;
int firstToRender;
int lastToRender;
int x;
int y;
int lineNumber;

if ( accelerated )
{
firstToRender = firstTokenNeedToRender( r );
lastToRender = lastTokenNeedToRender( r );
x = Geometry.LEFT_MARGIN_IN_PIXELS;
y = startAtBaseline;
lineNumber = startAtLineNumber;
firstTokenOnLine = true;
}
else
{
prepareAccelerator1();
firstToRender = 0;
lastToRender = tokens.length - 1;
x = Geometry.LEFT_MARGIN_IN_PIXELS;
y = Geometry.TOP_MARGIN_IN_PIXELS +
Geometry.LEADING_IN_PIXELS;
lineNumber = 1;
firstTokenOnLine = true;
}
if ( DEBUGGING )
{
System.out.println( "first:"
+ firstToRender
+ " last:"
+ lastToRender
+ " x:"
+ x
+ " ybaseline:"
+ y
+ " r.y:"
+ r.y
+ " r.height:"
+ r.height
+ " ln:"
+ lineNumber );
}
for ( int i = firstToRender; i <= lastToRender; i++ )
{
Token t = tokens[ i ];
if ( !accelerated && firstTokenOnLine )
{
// capture base line info of this line for the
accelerator.
lineRenderedAt( y, lineNumber, i );
}

if ( t instanceof NL )
{
// render blank lines compressed.
int lines = ( (NL)t ).getCount();
switch ( lines )
{
case 1 :
// single space
y += Geometry.LEADING_IN_PIXELS;
break;
case 2 :
// 1.5 spacing
y += ( Geometry.LEADING_IN_PIXELS * 15 / 10 );
break;
case 3 :
default :
// anything bigger, just double space.
y += ( Geometry.LEADING_IN_PIXELS * 2 );
break;
}
lineNumber += lines; // leave off line numbers, to
avoid
// scrunching
x = Geometry.LEFT_MARGIN_IN_PIXELS;
firstTokenOnLine = true;
}
else
{
// text-rendering or space token
if ( hasLineNumbers && firstTokenOnLine )
{
// draw the line number
g.setColor( Token.getLineNumberForeground() );
g.setFont( Token.getLineNumberFont() );
String digits = Integer.toString( lineNumber );
// right justify
int width = g.getFontMetrics().stringWidth( digits
);
g.drawString( digits, x + lineNumberWidth - width,
y );
x += lineNumberWidth
+ Geometry.LINE_NUMBER_MARGIN_IN_PIXELS;
}
g.setColor( t.getForeground() );
g.setFont( t.getFont() );
String text = t.getText();
g.drawString( text, x, y );
x += g.getFontMetrics().stringWidth( text );
firstTokenOnLine = false;
} // end else
} // end for
// we now have captured all token baselines, even if bandCount
was tiny.
if ( !accelerated )
{
prepareAccelerator2();
accelerated = true;
}

} // end paint

/**
* @param width
* in pixels of the scrollable region including room for
line numbers
* and margins, but not scrollbars.
* @param height
* in pixell of the srollable region including room for
margins, but
* not scrollbars.
* @param hasLineNumbers
* true if want line numbers applied down the left hand
side.
* @param lineNumberWidth
* with of the line number column
*/
public void set ( int width, int height, boolean hasLineNumbers,
int lineNumberWidth )

{
this.dimension = new Dimension( width, height );
this.setMinimumSize( dimension );
this.setPreferredSize( dimension );
this.setMaximumSize( dimension );
this.hasLineNumbers = hasLineNumbers;
this.lineNumberWidth = lineNumberWidth;

// adding or removing line numbers means new size.
// caller must setPreferred size
accelerated = false;
/*
* my canvas size has changed, even if only virtual. Warn
parent
* container.
*/
getParent().invalidate();
}

/**
* Set tokens to display
*
* @param tokens
* array of tokens, without lead or trailing NL()
* @param totalLines
* number of lines of text to render.
*/
public void setTokens ( Token[] tokens, int totalLines )
{
this.tokens = tokens;
this.totalLines = totalLines;
this.accelerated = false;
}

/**
* Constructor
*/
public PrettyCanvas()
{
}

} // end class PrettyCanvas
 
A

Andrew Thompson

You posted everywhere but where I expected to find it,
comp.lang.java.gui

Ummmm.. no. The OP X-posted (possibly at my prompting
on an earlier thread[3]) to..
comp.lang.java.programmer,comp.lang.java.gui,comp.lang.java.help

Note the middle one - comp.lang.java.gui.

After the initial post[1], I also asked[2] if they might be
so kind (and clever) as to figure how to both X-post *and*
set a 'Follow-Up To' a single group.

[1]
<http://groups.google.com/group/comp...60daeb5803d/542bc4680c7417ea#542bc4680c7417ea>
[2]
<http://groups.google.com/[email protected]>

[3] It is an effort to cut down on the multi-posting.

Note Follow-Ups to this post set to c.l.j.gui only..
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,050
Latest member
AngelS122

Latest Threads

Top