StyledDocument won't reset styles if a URL is clicked! (strange...help!)

I'm having a really wierd issue, that's exhibited by the SSCE below. If you do a next/prev on the messages available, everything works fine.. The problem occurs only if you click one of the highlighted URLs (in message 2), in which case clicking next and previous thereafter, shows entirely highlighted messages! (wrongfully so)

Your help on this one, is appreciated!

import javax.swing.*;

import javax.swing.text.*;

import java.awt.*;

import java.awt.event.*;

import java.util.regex.*;

publicfinalclass MessageViewerextends JFrame{

privatestaticfinallong serialVersionUID = 1;

privatestaticfinal Pattern PATTERN_URL = Pattern.compile("https?://([-\\w\\.]+)+(:\\d+)?(/([-\\w/_\\.]*(\\?\\S+)?)?)?", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE );

privatestaticfinal IntegerLINK_ATTRIBUTE= 1;

publicfinal String messages [] ={"This is a first message","http://google.com","this is another message"};

private JTextPaneoutputScreen;

private JScrollPaneoutputScrollPane;

privateintmessageIndex;

private JButton next, prev;

publicstaticvoid main( String args[] ){

MessageViewer m =new MessageViewer();

m.setVisible(true );

}

public MessageViewer(){

super("Message Viewer Test" );

setLayout(new BorderLayout() );

messageIndex = 0;

outputScreen =new JTextPane();

outputScreen.setEditable(false );

outputScreen.addMouseListener(new MouseAdapter(){

publicvoid mouseClicked( MouseEvent e ){

if( !e.isPopupTrigger() && SwingUtilities.isLeftMouseButton( e ) ){

if( Desktop.isDesktopSupported() ){

try{

StyledDocument doc = (StyledDocument)outputScreen.getDocument();

String s = (String)doc.getCharacterElement( outputScreen.viewToModel( e.getPoint() ) ).getAttributes().getAttribute( LINK_ATTRIBUTE );

if( s !=null )

Desktop.getDesktop().browse(new java.net.URI( s ) );

}

catch( Exception x ){

x.printStackTrace();

}

}

}

}

});

outputScrollPane =new JScrollPane( outputScreen, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS );

add( outputScrollPane, BorderLayout.CENTER );

final JPanel buttonPanel =new JPanel(new FlowLayout( FlowLayout.RIGHT ) );

next =new JButton(new NextMessageAction() );

prev =new JButton(new PreviousMessageAction() );

buttonPanel.add( prev );

buttonPanel.add( next );

add( buttonPanel, BorderLayout.AFTER_LAST_LINE );

setSize( 500, 500 );

setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

display( messages[0] );

indexFix();

}

/**

* Display a string, highlight the URLs

* @param str

*/

publicvoid display(final String str ){

DefaultStyledDocument doc =new DefaultStyledDocument();

outputScreen.setStyledDocument( doc );

outputScreen.setText( str );

Matcher matcher = PATTERN_URL.matcher( str );

Style s;

while( matcher.find () ){

s = doc.addStyle("link" + matcher.start(),null );

s.addAttribute( LINK_ATTRIBUTE, matcher.group() );

StyleConstants.setForeground( s, Color.blue );

StyleConstants.setBold( s,true );

doc.setCharacterAttributes( matcher.start(), matcher.end() - matcher.start(), s,true );

}

}

/**

* Enable/disable buttons

*/

publicvoid indexFix(){

next.setEnabled( messageIndex + 1 != messages.length );

prev.setEnabled( messageIndex != 0 );

}

privateclass NextMessageActionextends AbstractAction{

privatestaticfinallong serialVersionUID = 1;

public NextMessageAction(){

putValue( Action.NAME,"Next Message" );

}

publicvoid actionPerformed( ActionEvent e ){

display( messages[ ++messageIndex ] );

indexFix();

repaint();

}

}

privateclass PreviousMessageActionextends AbstractAction{

privatestaticfinallong serialVersionUID = 1;

public PreviousMessageAction(){

putValue( Action.NAME,"Previous Message" );

}

publicvoid actionPerformed( ActionEvent e ){

display( messages[ --messageIndex ] );

indexFix();

repaint();

}

}

}

[8408 byte] By [Saevena] at [2007-11-27 9:42:03]
# 1
In fact, you can remove the mouselistener altogether and still reproduce the problem...
Saevena at 2007-7-12 23:44:42 > top of Java-index,Desktop,Core GUI APIs...
# 2

I don't use JDK6 so I can't use your code as posted.

But I made the following change and it works fine for me uisng JDK1.4.2 on XP:

if( !e.isPopupTrigger() && SwingUtilities.isLeftMouseButton( e ) ){

//if( Desktop.isDesktopSupported() ){

try{

StyledDocument doc = (StyledDocument)outputScreen.getDocument();

String s = (String)doc.getCharacterElement( outputScreen.viewToModel( e.getPoint() ) ).getAttributes().getAttribute( LINK_ATTRIBUTE );

System.out.println(s);

String[] cmd = new String[4];

cmd[0] = "cmd.exe";

cmd[1] = "/C";

cmd[2] = "start";

cmd[3] = s;

Process process = Runtime.getRuntime().exec( cmd );

//if( s != null )

//Desktop.getDesktop().browse( new java.net.URI( s ) );

}

catch( Exception x ){

x.printStackTrace();

}

//}

}

See if the above code works. If it does then the problem is with the Desktop class. If it doesn't then I guess the problem is with the JDK.

You could try wrapping the invocation of the Desktop in a SwingUtilities.invokeLater(). Just a wild guess.

camickra at 2007-7-12 23:44:42 > top of Java-index,Desktop,Core GUI APIs...
# 3

Hi Camickr,

Thanks for the post, but apparently, this SSCE without any of the desktop tidbits will still reproduce the problem:

import javax.swing.*;

import javax.swing.text.*;

import java.awt.*;

import java.awt.event.*;

import java.util.regex.*;

public final class MessageViewer extends JFrame {

private static final long serialVersionUID = 1;

private static final Pattern PATTERN_URL = Pattern.compile( "https?://([-\\w\\.]+)+(:\\d+)?(/([-\\w/_\\.]*(\\?\\S+)?)?)?", Pattern.MULTILINE | Pattern.CASE_INSENSITIVE );

private static final IntegerLINK_ATTRIBUTE= 1;

public final String messages [] = { "This is a first message", "http://google.com", "this is another message" };

private JTextPaneoutputScreen;

private JScrollPaneoutputScrollPane;

private intmessageIndex;

private JButton next, prev;

public static void main( String args[] ){

MessageViewer m = new MessageViewer();

m.setVisible( true );

}

public MessageViewer(){

super( "Message Viewer Test" );

setLayout( new BorderLayout() );

messageIndex = 0;

outputScreen = new JTextPane();

outputScreen.setEditable( false );

outputScrollPane = new JScrollPane( outputScreen, ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, ScrollPaneConstants.HORIZONTAL_SCROLLBAR_ALWAYS );

add( outputScrollPane, BorderLayout.CENTER );

final JPanel buttonPanel = new JPanel( new FlowLayout( FlowLayout.RIGHT ) );

next = new JButton( new NextMessageAction() );

prev = new JButton( new PreviousMessageAction() );

buttonPanel.add( prev );

buttonPanel.add( next );

add( buttonPanel, BorderLayout.AFTER_LAST_LINE );

setSize( 500, 500 );

setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );

display( messages[0] );

indexFix();

}

/**

* Display a string, highlight the URLs

* @param str

*/

public void display( final String str ){

DefaultStyledDocument doc = new DefaultStyledDocument();

outputScreen.setStyledDocument( doc );

outputScreen.setText( str );

Matcher matcher = PATTERN_URL.matcher( str );

Style s;

while( matcher.find () ){

s = doc.addStyle( "link" + matcher.start(), null );

s.addAttribute( LINK_ATTRIBUTE, matcher.group() );

StyleConstants.setForeground( s, Color.blue );

StyleConstants.setBold( s, true );

doc.setCharacterAttributes( matcher.start(), matcher.end() - matcher.start(), s, true );

}

}

/**

* Enable/disable buttons

*/

public void indexFix(){

next.setEnabled( messageIndex + 1 != messages.length );

prev.setEnabled( messageIndex != 0 );

}

private class NextMessageAction extends AbstractAction{

private static final long serialVersionUID = 1;

public NextMessageAction(){

putValue( Action.NAME, "Next Message" );

}

public void actionPerformed( ActionEvent e ){

display( messages[ ++messageIndex ] );

indexFix();

repaint();

}

}

private class PreviousMessageAction extends AbstractAction{

private static final long serialVersionUID = 1;

public PreviousMessageAction(){

putValue( Action.NAME, "Previous Message" );

}

public void actionPerformed( ActionEvent e ){

display( messages[ --messageIndex ] );

indexFix();

repaint();

}

}

}

So we can rule out the action as the culprit. Bug with JDK 6? I am using:

java version "1.6.0_01"

Java(TM) SE Runtime Environment (build 1.6.0_01-b06)

Java HotSpot(TM) Client VM (build 1.6.0_01-b06, mixed mode, sharing)

Saevena at 2007-7-12 23:44:42 > top of Java-index,Desktop,Core GUI APIs...
# 4
I keep hitting the next and previous buttons. The first and third message are always black text. The second message is blue text (indicating a link).So It appears it still works for me and JDK6 is the problem.
camickra at 2007-7-12 23:44:44 > top of Java-index,Desktop,Core GUI APIs...
# 5
Click on the blue link tho Camickr (even though it does nothing), and then go back/forth anew. In my case, it makes the other paragraphs blue as well.
Saevena at 2007-7-12 23:44:44 > top of Java-index,Desktop,Core GUI APIs...
# 6

In fact, one doesn't even need to click on the blue text itself, but anywhere in the JTextPane while the blue text is being displayed.

This is really odd, since we're even creating a new StyledDocument before every display.

Do the style changes bubble upward from the document to the JTextPane? Which events get fired internally by a mouse click with no listener attached?

Saevena at 2007-7-12 23:44:44 > top of Java-index,Desktop,Core GUI APIs...
# 7

Something to note, if you replace this line:

doc.setCharacterAttributes( matcher.start(), matcher.end() - matcher.start(), s, true );

with

doc.setCharacterAttributes( matcher.start(), matcher.end() - matcher.start() - 1, s, true );

It appears that the issue does not occur (though the styling goes one character short).

Saevena at 2007-7-12 23:44:44 > top of Java-index,Desktop,Core GUI APIs...
# 8

I've installed 1.5 build 12, and the problem continues. I've also updated to JDK6 u2 and it exists nonetheless. Off to file a bug report.

Creating a new JTextPane with every run, however evil, takes care of the problem temporarily

public void display( final String str ){

outputScreen = new JTextPane();

outputScreen.setEditable( false );

outputScrollPane.setViewportView( outputScreen );

DefaultStyledDocument doc = new DefaultStyledDocument();

outputScreen.setStyledDocument( doc );

outputScreen.setText( str );

Matcher matcher = PATTERN_URL.matcher( str );

Style s;

while( matcher.find () ){

s = doc.addStyle( "link" + matcher.start(), null );

s.addAttribute( LINK_ATTRIBUTE, matcher.group() );

StyleConstants.setForeground( s, Color.blue );

StyleConstants.setBold( s, true );

doc.setCharacterAttributes( matcher.start(), matcher.end() - matcher.start(), s, true );

}

}

Saevena at 2007-7-12 23:44:44 > top of Java-index,Desktop,Core GUI APIs...