viernes, 5 de septiembre de 2008

Netbeans Visual Library and a Multiline Label Widget

Some days ago I was trying to find a solution for a multiline label widget for the netbeans visual library, I tried some solutions (out of the box) like QLabel and others found on the web, without any success, after 2 weeks of frustrating research, I decided to continue with other features of my system, and then later on, I returned to this issue again and give it a try.

The problem:
I needed a Widget that supported Multiline label, auto-fit on resize, and drag and drop action, the first approach was to include QLabel within a ComponentWidget, It did not work for me, and then, I tried with a JTextArea in this way:


public class MultilineLabelWidget
extends ComponentWidget
{
public MultilineLabelWidget( Scene scene, String text )
{
super( scene, new JTextArea( text ) );
JTextArea jText = (JTextArea) this.getComponent();
jText.setOpaque( false );
jText.setEditable( false );
jText.setLineWrap( true );
jText.setWrapStyleWord( true );
jText.setHighlighter( null );
jText.setBorder( BorderFactory.createEmptyBorder() );

this.getActions().addAction( ActionFactory.createResizeAction() );
this.getActions().addAction( ActionFactory.createMoveAction() );
this.setBorder( org.netbeans.api.visual.border.BorderFactory.createLineBorder( 8 ) );
}
}

and It partially worked!!, the problem is that JTextArea is painted after the widget, so the MoveAction will not work! out-of-the-box, ummm, I tried another solution, not extending the ComponentWidget but creating an in-line ComponentWidget and trying to catch the Mouse Events and redirect the calls to its Action in the Widget, like this:

JTextArea jText = new JTextArea( hm );
jText.setOpaque( false );
jText.setEditable( false );
jText.setLineWrap( true );
jText.setWrapStyleWord( true );
jText.setBorder( BorderFactory.createEmptyBorder() );
final ComponentWidget widget = new ComponentWidget(scene,jText);
widget.getActions().addAction( ActionFactory.createResizeAction() );
widget.getActions().addAction( ActionFactory.createMoveAction() );
jText.addMouseMotionListener( new MouseMotionListener(){

@Override
public void mouseDragged( MouseEvent event )
{
widget.getActions().mouseDragged( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}

@Override
public void mouseMoved( MouseEvent event )
{
widget.getActions().mouseMoved( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}} );

jText.addMouseListener( new MouseListener(){

@Override
public void mouseClicked( MouseEvent event )
{
widget.getActions().mouseClicked( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}

@Override
public void mouseEntered( MouseEvent event )
{
widget.getActions().mouseEntered( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}

@Override
public void mouseExited( MouseEvent event )
{
widget.getActions().mouseExited( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}

@Override
public void mousePressed( MouseEvent event )
{
widget.getActions().mousePressed( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}

@Override
public void mouseReleased( MouseEvent event )
{
widget.getActions().mouseReleased( widget, new WidgetAction.WidgetMouseEvent(new Date().getTime(),event) );
}} );
jText.setHighlighter( null );
widget.setBorder( org.netbeans.api.visual.border.BorderFactory.createLineBorder( 8 ) );

It worked better than the previous solution!, BUT!!!!!!!!!, It just worked when I moved the mouse pointer slowly (of course, first click and then drag), but every time I moved the mouse pointer as I usually do, the component was not repainted (It did not followed the mouse pointer), and at the moment I released the left button on the mouse, the widget was painted!, frustrating for me because I think It should be easy to do it! (please developer of Visual Library, include a Multiline Label Widget Out-of-the-box).

The solution
Finally, I found a code on the net (It references to com.exalto.UI) and adapted it to fit within the Visual Library Widget, so the result It is this:

package test.designer.widget;

import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.font.LineBreakMeasurer;
import java.awt.font.TextAttribute;
import java.awt.font.TextLayout;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;

import org.netbeans.api.visual.widget.LabelWidget;
import org.netbeans.api.visual.widget.Scene;

public class MultilineLabelWidget
extends LabelWidget
{
private boolean justify;

public MultilineLabelWidget( Scene scene, String label )
{
super( scene );
this.setLabel( label );
this.setJustified( true );
}

@Override
protected void paintWidget()
{
paintOrGetSize( this.getGraphics() );
}

private void paintOrGetSize( Graphics2D gr )
{
float width = (float) ( this.getBounds() != null ? this.getBounds().getWidth() : this.getPreferredSize()
.getWidth() );
Insets insets = this.getBorder().getInsets();
float rwidth = width - ( insets.left + insets.right );// + margin.left + margin.right;
Rectangle rec = this.calculateClientArea();
gr.setFont( getFont() );

float x = 0.0F;//+ margin.left;
float y = (float) rec.getY();//+ margin.top;

if ( rwidth > 0 && this.getLabel() != null && this.getLabel().length() > 0 )
{
AttributedString as = new AttributedString( this.getLabel() );
as.addAttribute( TextAttribute.FONT, getFont() );
AttributedCharacterIterator aci = as.getIterator();
LineBreakMeasurer lbm = new LineBreakMeasurer( aci, gr.getFontRenderContext() );

while ( lbm.getPosition() < aci.getEndIndex() )
{
TextLayout textLayout = lbm.nextLayout( rwidth );
if ( gr != null && isJustified() && textLayout.getVisibleAdvance() > 0.80 * rwidth )
{
textLayout = textLayout.getJustifiedLayout( rwidth );
}
if ( gr != null )
{
textLayout.draw( gr, x, y + textLayout.getAscent() );
}
y += textLayout.getDescent() + textLayout.getLeading() + textLayout.getAscent();

}
}
}

public boolean isJustified()
{
return justify;
}

public void setJustified( boolean justify )
{
boolean old = this.justify;
this.justify = justify;
if ( old != this.justify )
{
repaint();
}
}
}

It worked for me, It worked for my purposes, I hope It works for you.
Please modify it, improve the code, see if It works for you, and feedback to me.

Thanks a lot.

References:
http://graph.netbeans.org/servlets/ReadMsg?listName=users&msgNo=1295
http://graph.netbeans.org/servlets/ReadMsg?list=users&msgNo=726
http://www.koders.com/java/fid9BE9B31AC9BED01828448BF91A61AFA5AE431E16.aspx

9 comentarios:

Anónimo dijo...

I solved the problem by creating a widget with some LabelWidget inside it... It worked for me.

Anónimo dijo...

Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now keep it up!

Anónimo dijo...
Este blog ha sido eliminado por un administrador de blog.
Rajiv Jha dijo...

Hi Barenca,

I saw your post and seems like i am also in similar situation where i need to have a multiline label widget like MS word flowchart(rectancle / squares .. where you type in some long texts). How ever when i tried to use you solution .. the labels beacme invisible only on mouse hovering it showed up . but still no line break ups (i.e. still in single line). it would be great if you can post or send personally a samll but working example .. it would be really helpful.

Thanks
Rajiv Jha
rajive.jha@gmail.com

Anónimo dijo...

Just really love you guys… what fantastic and well researched information. Thank you bunches.

Anónimo dijo...

You are so nice to share these with us.

Anónimo dijo...

A useful tip

Anónimo dijo...
Este blog ha sido eliminado por un administrador de blog.
Anónimo dijo...
Este blog ha sido eliminado por un administrador de blog.