problems with custom underline in JTextPane

Hi!

I found in this forum some sample code for custom underline (jagged underline) that was written by Stas. Thanks for sharing it!

I had to make some changes to satisfy my requirements: I want to use jagged underlines in red and green and I want to underline e.g. black text red or green.

I extended DefaultStyledEditorKit like Stas does in his solution. Please see the sample code below.

The problem is that the underlines are not painted correctly when you select multiple lines and change the style of the underline by clicking one of the buttons in the sample-app. Only the first and the last line of the selection is rendered with the selected underline, the lines between are not changed. If you force to repaint the lines between that are not rendered correctly by selecting a few characters of it and select a custom underline again, the underline you selected before is painted, too. So I guess that the LabelViews are not painted correctly. TextPane.repaint does not help. So how can I force that all LabelView of a selection are painted? Or maybe I have done some other mistake.

Any suggestions are highly appreciated.

import java.awt.BorderLayout;

import java.awt.Color;

import java.awt.Graphics;

import java.awt.Shape;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.*;

import javax.swing.text.*;

class Testimplements ActionListener{

JTextPane pane;

public Test(){

JPanel p =new JPanel();

JButton green =new JButton("green");

green.addActionListener(this);

p.add(green);

JButton greenJagged =new JButton("greenJagged");

greenJagged.addActionListener(this);

p.add(greenJagged);

JButton red =new JButton("red");

red.addActionListener(this);

p.add(red);

JButton redJagged =new JButton("redJagged");

redJagged.addActionListener(this);

p.add(redJagged);

JButton reset =new JButton("reset");

reset.addActionListener(this);

p.add(reset);

JFrame fr =new JFrame("TEST");

fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

pane =new JTextPane();

pane.setEditorKit(new ExtendedEditorKit());

String text ="test test test test\n";

for (int i = 0; i < 20; i++){

text +="test test test test\n";

}

pane.setText(text);

pane.setEditable(false);

JScrollPane sp =new JScrollPane(pane);

fr.getContentPane().add(sp);

fr.getContentPane().add(p, BorderLayout.NORTH);

fr.setSize(500, 500);

fr.setVisible(true);

}

publicstaticvoid main(String[] args){

new Test();

}

publicvoid actionPerformed(ActionEvent e){

MutableAttributeSet as =new SimpleAttributeSet();

if (e.getActionCommand().equals("green")){

setCustomUnderline(as, GREEN_UNDERLINE);

}elseif (e.getActionCommand().equals("red")){

setCustomUnderline(as, RED_UNDERLINE);

}elseif (e.getActionCommand().equals("redJagged")){

setCustomUnderline(as, RED_JAGGED_UNDERLINE);

}elseif (e.getActionCommand().equals("greenJagged")){

setCustomUnderline(as, GREEN_JAGGED_UNDERLINE);

}elseif (e.getActionCommand().equals("reset")){

setCustomUnderline(as, NO_CUSTOM_UNDERLINE);

}

pane.setCharacterAttributes(as,false);

// pane.revalidate(); // does not help...

// pane.repaint(); // does not help...

}

publicclass ExtendedEditorKitextends StyledEditorKit{

public ViewFactory getViewFactory(){

returnnew ExtendedViewFactory();

}

}

class ExtendedViewFactoryimplements ViewFactory{

public View create(Element elem){

String kind = elem.getName();

if (kind !=null){

if (kind.equals(AbstractDocument.ContentElementName)){

int customUnderlineType = getCustomUnderline(elem

.getAttributes());

if (customUnderlineType == GREEN_JAGGED_UNDERLINE){

returnnew JaggedLabelView(elem, Color.green);

}elseif (customUnderlineType == RED_JAGGED_UNDERLINE){

returnnew JaggedLabelView(elem, Color.red);

}elseif (customUnderlineType == GREEN_UNDERLINE){

returnnew ColorLabelView(elem, Color.green);

}elseif (customUnderlineType == RED_UNDERLINE){

returnnew ColorLabelView(elem, Color.red);

}else

returnnew LabelView(elem);

}elseif (kind.equals(AbstractDocument.ParagraphElementName)){

returnnew ParagraphView(elem);

}elseif (kind.equals(AbstractDocument.SectionElementName)){

returnnew BoxView(elem, View.Y_AXIS);

}elseif (kind.equals(StyleConstants.ComponentElementName)){

returnnew ComponentView(elem);

}elseif (kind.equals(StyleConstants.IconElementName)){

returnnew IconView(elem);

}

}

returnnew LabelView(elem);

}

}

class JaggedLabelViewextends LabelView{

private Color color;

public JaggedLabelView(Element elem, Color color){

super(elem);

this.color = color;

}

publicvoid paint(Graphics g, Shape allocation){

super.paint(g, allocation);

paintJaggedLine(g, allocation);

}

publicvoid paintJaggedLine(Graphics g, Shape a){

int y = (int) (a.getBounds().getY() + a.getBounds().getHeight());

int x1 = (int) a.getBounds().getX();

int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth());

Color old = g.getColor();

g.setColor(color);

int w = 3;

int h = 2;

for (int i = x1; i <= x2; i += w * 2){

g.drawArc(i + 0, y - h, w, h, 0, 180);

g.drawArc(i + w, y - h, w, h, 180, 181);

}

g.setColor(old);

}

}

class ColorLabelViewextends LabelView{

private Color color;

public ColorLabelView(Element elem, Color color){

super(elem);

this.color = color;

}

publicvoid paint(Graphics g, Shape allocation){

super.paint(g, allocation);

paintLine(g, allocation);

}

publicvoid paintLine(Graphics g, Shape a){

int y = (int) (a.getBounds().getY() + a.getBounds().getHeight());

int x1 = (int) a.getBounds().getX();

int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth());

Color old = g.getColor();

g.setColor(color);

g.drawLine(x1, y - 2, x2, y - 2);

g.setColor(old);

}

}

publicstaticfinal String CustomUnderline ="customUnderline";

publicstaticfinalint RED_UNDERLINE = 1;

publicstaticfinalint GREEN_UNDERLINE = 2;

publicstaticfinalint RED_JAGGED_UNDERLINE = 3;

publicstaticfinalint GREEN_JAGGED_UNDERLINE = 4;

publicstaticfinalint NO_CUSTOM_UNDERLINE = -1;

publicstaticvoid setCustomUnderline(MutableAttributeSet a,int type){

a.addAttribute(CustomUnderline,new Integer(type));

}

publicstaticint getCustomUnderline(AttributeSet a){

Object o = a.getAttribute(CustomUnderline);

if (o !=null){

try{

return Integer.parseInt(o.toString());

}catch (Exception x){

return NO_CUSTOM_UNDERLINE;

}

}

return NO_CUSTOM_UNDERLINE;

}

}

[15252 byte] By [the12huntersa] at [2007-11-27 3:15:28]
# 1
Anybody who can help me?Thank you for your time!
the12huntersa at 2007-7-12 8:17:59 > top of Java-index,Desktop,Core GUI APIs...
# 2

The problem is your views creation. The underlined views are created only if an appropriate attribute is defined. So when you select whole line and apply the attribute view isn't recreated, the same simple LabelView remains.

But when a part of label is selected the old view is separated to be 2 parts first part (without underline) remains the same LabelView but the second part is recreated and a new UnderlinedLabelView is created.

The solution is to use one ExtendedLabelView class for all content elements. But in the paint() method check whether getAttributes() contains proper underlying attributes and call the

paintJaggedLine()

or

paintLine() if appropriate attribute is defined only.

in fact it will be

super.paint();

int customUnderlineType = getCustomUnderline(getAttributes());

if (customUnderlineType == GREEN_JAGGED_UNDERLINE) {

paintJaggedLine();

}

...

Regards,

Stas

StanislavLa at 2007-7-12 8:17:59 > top of Java-index,Desktop,Core GUI APIs...
# 3

Stas, thank you!

I post the changed sample for others who might be interested.

import java.awt.BorderLayout;

import java.awt.Color;

import java.awt.Graphics;

import java.awt.Shape;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import javax.swing.*;

import javax.swing.text.*;

class Test implements ActionListener {

JTextPane pane;

public Test() {

JPanel p = new JPanel();

JButton green = new JButton("green");

green.addActionListener(this);

p.add(green);

JButton greenJagged = new JButton("greenJagged");

greenJagged.addActionListener(this);

p.add(greenJagged);

JButton red = new JButton("red");

red.addActionListener(this);

p.add(red);

JButton redJagged = new JButton("redJagged");

redJagged.addActionListener(this);

p.add(redJagged);

JButton reset = new JButton("reset");

reset.addActionListener(this);

p.add(reset);

JFrame fr = new JFrame("TEST");

fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

pane = new JTextPane();

pane.setEditorKit(new ExtendedEditorKit());

String text = "test test test test\n";

for (int i = 0; i < 20; i++) {

text += "test test test test\n";

}

pane.setText(text);

pane.setEditable(false);

JScrollPane sp = new JScrollPane(pane);

fr.getContentPane().add(sp);

fr.getContentPane().add(p, BorderLayout.NORTH);

fr.setSize(500, 500);

fr.setVisible(true);

}

public static void main(String[] args) {

new Test();

}

public void actionPerformed(ActionEvent e) {

MutableAttributeSet as = new SimpleAttributeSet();

if (e.getActionCommand().equals("green")) {

setCustomUnderline(as, GREEN_UNDERLINE);

} else if (e.getActionCommand().equals("red")) {

setCustomUnderline(as, RED_UNDERLINE);

} else if (e.getActionCommand().equals("redJagged")) {

setCustomUnderline(as, RED_JAGGED_UNDERLINE);

} else if (e.getActionCommand().equals("greenJagged")) {

setCustomUnderline(as, GREEN_JAGGED_UNDERLINE);

} else if (e.getActionCommand().equals("reset")) {

setCustomUnderline(as, NO_CUSTOM_UNDERLINE);

}

pane.setCharacterAttributes(as, false);

}

public class ExtendedEditorKit extends StyledEditorKit {

public ViewFactory getViewFactory() {

return new ExtendedViewFactory();

}

}

class ExtendedViewFactory implements ViewFactory {

public View create(Element elem) {

String kind = elem.getName();

if (kind != null) {

if (kind.equals(AbstractDocument.ContentElementName)) {

return new ExtendedLabelView(elem);

} else if (kind.equals(AbstractDocument.ParagraphElementName)) {

return new ParagraphView(elem);

} else if (kind.equals(AbstractDocument.SectionElementName)) {

return new BoxView(elem, View.Y_AXIS);

} else if (kind.equals(StyleConstants.ComponentElementName)) {

return new ComponentView(elem);

} else if (kind.equals(StyleConstants.IconElementName)) {

return new IconView(elem);

}

}

return new LabelView(elem);

}

}

class ExtendedLabelView extends LabelView {

public ExtendedLabelView(Element elem) {

super(elem);

}

public void paint(Graphics g, Shape allocation) {

super.paint(g, allocation);

int customUnderlineType = getCustomUnderline(this.getElement()

.getAttributes());

if (customUnderlineType == GREEN_JAGGED_UNDERLINE) paintJaggedLine(g, allocation, Color.green);

else if (customUnderlineType == RED_JAGGED_UNDERLINE) paintJaggedLine(g, allocation, Color.red);

else if (customUnderlineType == GREEN_UNDERLINE) paintLine(g, allocation, Color.green);

else if (customUnderlineType == RED_UNDERLINE) paintLine(g, allocation, Color.red);

}

public void paintJaggedLine(Graphics g, Shape a, Color color) {

int y = (int) (a.getBounds().getY() + a.getBounds().getHeight());

int x1 = (int) a.getBounds().getX();

int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth());

Color old = g.getColor();

g.setColor(color);

int w = 3;

int h = 2;

for (int i = x1; i <= x2; i += w * 2) {

g.drawArc(i + 0, y - h, w, h, 0, 180);

g.drawArc(i + w, y - h, w, h, 180, 181);

}

g.setColor(old);

}

public void paintLine(Graphics g, Shape a, Color color) {

int y = (int) (a.getBounds().getY() + a.getBounds().getHeight());

int x1 = (int) a.getBounds().getX();

int x2 = (int) (a.getBounds().getX() + a.getBounds().getWidth());

Color old = g.getColor();

g.setColor(color);

g.drawLine(x1, y - 2, x2, y - 2);

g.setColor(old);

}

}

public static final String CustomUnderline = "customUnderline";

public static final int RED_UNDERLINE = 1;

public static final int GREEN_UNDERLINE = 2;

public static final int RED_JAGGED_UNDERLINE = 3;

public static final int GREEN_JAGGED_UNDERLINE = 4;

public static final int NO_CUSTOM_UNDERLINE = -1;

public static void setCustomUnderline(MutableAttributeSet a, int type) {

a.addAttribute(CustomUnderline, new Integer(type));

}

public static int getCustomUnderline(AttributeSet a) {

Object o = a.getAttribute(CustomUnderline);

if (o != null) {

try {

return Integer.parseInt(o.toString());

} catch (Exception x) {

return NO_CUSTOM_UNDERLINE;

}

}

return NO_CUSTOM_UNDERLINE;

}

}

the12huntersa at 2007-7-12 8:17:59 > top of Java-index,Desktop,Core GUI APIs...