Skip to Main Content

Java SE (Java Platform, Standard Edition)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

Frame freezes on a click on a menu item, but the menu item's shortkey works

843806Jun 17 2008 — edited Jun 20 2008
Hello, I have a problem with a swing application that contains a JTextPane with syntax highlighting and undo/redo operations.

In the following steps I describe two scenarios.
The first one freezes the whole application, and the second one is working.
But both scenarios are executing the same lines of code.


The freeze:
1. Start the application
2. Type an 'a'
3. Click on the menu item 'Edit->Undo'
4. Click on the menu item 'Edit->Redo'
5. FREEZE

The working one:
1. Start the application
2. Write an 'a'
3. Use the keystroke 'ctrl-Z' to undo
4. Use the keystroke 'ctrl-Y' to redo
5. Everything works fine

I hope somebody can help me, here is the complete test case:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.*;
import java.util.Iterator;
import java.util.LinkedHashMap;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.undo.UndoManager;

public class EditorGUI
{
  private JFrame jFrame = null;

  private JTextPane jTextPaneEditor = null;

  private JMenuBar jMenuBar = null;

  private JMenu jMenuEdit = null;

  private JMenuItem jMenuItemUndo = null;

  private JMenuItem jMenuItemRedo = null;

  private UndoManager undoManager = null;

  /**
   * Constrcuts a new instance of the class <code>EditorGUI</code>.
   */
  public EditorGUI()
  {
    // Set the system look and feel
    try
    {
      UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
    }
    catch (final Exception exception)
    {
      // Ignore exceptions at this point
    }
  }

  /**
   * The main method.
   * @param args The application arguments
   */
  public static void main(final String[] args)
  {
    new EditorGUI().getJFrame().setVisible(true);
  }

  /**
   * Returns the main frame.
   * @return The main frame
   */
  public JFrame getJFrame()
  {
    if (this.jFrame == null)
    {
      this.jFrame = new JFrame();
      this.jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      this.jFrame.setSize(800, 600);
      this.jFrame.setContentPane(new JPanel());
      this.jFrame.setJMenuBar(this.getJMenuBar());
      this.jFrame.getContentPane().setLayout(new BorderLayout());
      this.jFrame.getContentPane().add(this.getJTextPaneEditor(), BorderLayout.CENTER);
    }
    return this.jFrame;
  }

  /**
   * Returns the menu bar.
   * @return The menu bar
   */
  public JMenuBar getJMenuBar()
  {
    if (this.jMenuBar == null)
    {
      this.jMenuBar = new JMenuBar();
      this.jMenuBar.add(this.getJMenuEdit());
    }
    return this.jMenuBar;
  }

  /**
   * Returns the menu for edit operations.
   * @return The menu for edit operations
   */
  public JMenu getJMenuEdit()
  {
    if (this.jMenuEdit == null)
    {
      this.jMenuEdit = new JMenu("Edit");
      this.jMenuEdit.add(this.getJMenuItemUndo());
      this.jMenuEdit.add(this.getJMenuItemRedo());
    }
    return this.jMenuEdit;
  }

  /**
   * Returns the menu item for redo operations inside of the configuration editor.
   * @return The menu item for redo operations
   */
  public JMenuItem getJMenuItemRedo()
  {
    if (this.jMenuItemRedo == null)
    {
      this.jMenuItemRedo = new JMenuItem("Redo");
      final KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK);
      this.jMenuItemRedo.setAccelerator(keyStroke);
      this.jMenuItemRedo.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(final ActionEvent event)
        {
          // Fire the redo operation
          if (EditorGUI.this.getUndoManager().canRedo())
          {
            EditorGUI.this.getUndoManager().redo();
          }
        }
      });
    }
    return this.jMenuItemRedo;
  }

  /**
   * Returns the menu item for undo operations inside of the configuration editor.
   * @return The menu item for undo operations
   */
  public JMenuItem getJMenuItemUndo()
  {
    if (this.jMenuItemUndo == null)
    {
      this.jMenuItemUndo = new JMenuItem("Undo");
      final KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_Z, InputEvent.CTRL_DOWN_MASK);
      this.jMenuItemUndo.setAccelerator(keyStroke);
      this.jMenuItemUndo.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(final ActionEvent event)
        {
          // Fire the undo operation
          if (EditorGUI.this.getUndoManager().canUndo())
          {
            EditorGUI.this.getUndoManager().undo();
          }
        }
      });
    }
    return this.jMenuItemUndo;
  }

  /**
   * Returns the text pane which realizes the configuration editor.
   * @return The text pane which realizes the configuration editor
   */
  public JTextPane getJTextPaneEditor()
  {
    if (this.jTextPaneEditor == null)
    {
      this.jTextPaneEditor = new JTextPane();
      // Create the syntax highlighter
      final SyntaxHighlighter syntaxHighlighter = new SyntaxHighlighter(this.jTextPaneEditor, null);
      syntaxHighlighter.addHighlight("\"", "\"", new Color(0, 0, 192));
      syntaxHighlighter.addHighlight("'", "'", new Color(0, 0, 192));
      syntaxHighlighter.addHighlight("/**", "*/", new Color(63, 95, 191));
      syntaxHighlighter.addHighlight("/*", "*/", new Color(63, 127, 95));
      syntaxHighlighter.addHighlight("//", "\n", new Color(63, 127, 95));
      syntaxHighlighter.addHighlight("#", "\n", new Color(63, 127, 95));
      // Add the undo manager
      final Document document = this.jTextPaneEditor.getDocument();
      document.addUndoableEditListener(this.getUndoManager());
    }
    return this.jTextPaneEditor;
  }

  /**
   * Returns the undo manager for undo/redo operations.
   * @return The undo manager for undo/redo operations
   */
  public UndoManager getUndoManager()
  {
    if (this.undoManager == null)
    {
      this.undoManager = new UndoManager()
      {
        private static final long serialVersionUID = 998930996883778616L;

        @Override
        public void undoableEditHappened(final UndoableEditEvent undoableEditEvent)
        {
          AbstractDocument.DefaultDocumentEvent defaultDocumentEvent;
          defaultDocumentEvent = (AbstractDocument.DefaultDocumentEvent) undoableEditEvent.getEdit();
          if (defaultDocumentEvent.getType().equals(DocumentEvent.EventType.INSERT)
              || defaultDocumentEvent.getType().equals(DocumentEvent.EventType.REMOVE))
          {
            this.addEdit(undoableEditEvent.getEdit());
          }
        }
      };
      this.getUndoManager().discardAllEdits();
    }
    return this.undoManager;
  }
}

class SyntaxHighlighter implements Runnable, DocumentListener
{
  private final LinkedHashMap<String[], Style> highlightMap;

  private final StyleContext styleContext;

  private final StyledDocument styledDocument;

  private final Style mainStyle;

  /**
   * Constructs a new instance of the class <code>SyntaxHighlighter</code>.
   * @param textComponent The text component to use for syntax highlighting
   * @param mainStyle The main style to use for the text display
   */
  public SyntaxHighlighter(final JTextComponent textComponent, final Style mainStyle)
  {
    this.highlightMap = new LinkedHashMap<String[], Style>();
    this.styleContext = new StyleContext();
    this.styledDocument = new DefaultStyledDocument(this.styleContext);
    textComponent.setDocument(this.styledDocument);
    // Set the main document style
    if (mainStyle != null)
    {
      this.mainStyle = mainStyle;
    }
    else
    {
      final Style defaultStyle = this.styleContext.getStyle(StyleContext.DEFAULT_STYLE);
      this.mainStyle = this.styleContext.addStyle("mainStyle", defaultStyle);
      StyleConstants.setForeground(this.mainStyle, Color.black);
    }
    // Add the document listener
    this.styledDocument.addDocumentListener(this);
  }

  /**
   * Adds a new highlight to this <code>SyntaxHighlighter</code>.
   * @param keyword The keyword to be highlighted
   * @param color The color to use for highlighting
   */
  public void addHighlight(final String keyword, final Color color)
  {
    final Style style = this.styleContext.addStyle(null, null);
    StyleConstants.setForeground(style, color);
    this.highlightMap.put(new String[] { keyword, null }, style);
  }

  /**
   * Adds a new highlight to this <code>SyntaxHighlighter</code>.
   * @param startTag The start tag of the text to be highlighted
   * @param endTag The end tag of the text to be highlighted
   * @param color The color to use for highlighting
   */
  public void addHighlight(final String startTag, final String endTag, final Color color)
  {
    final Style style = this.styleContext.addStyle(null, null);
    StyleConstants.setForeground(style, color);
    this.highlightMap.put(new String[] { startTag, endTag }, style);
  }

  @Override
  public void changedUpdate(final DocumentEvent event)
  {
    // Empty implementation
  }

  /**
   * Removes all highlights from this <code>SyntaxHighlighter</code>.
   */
  public void clearHighlights()
  {
    this.highlightMap.clear();
  }

  @Override
  public void insertUpdate(final DocumentEvent event)
  {
    if (event.getDocument().getLength() > 0)
    {
      SwingUtilities.invokeLater(SyntaxHighlighter.this);
    }
  }

  @Override
  public void removeUpdate(final DocumentEvent event)
  {
    if (event.getDocument().getLength() > 0)
    {
      SwingUtilities.invokeLater(SyntaxHighlighter.this);
    }
  }

  @Override
  public void run()
  {
    try
    {
      final String text = this.styledDocument.getText(0, this.styledDocument.getLength());
      this.styledDocument.setCharacterAttributes(0, text.length(), this.mainStyle, false);
      int documentPointer = 0;
      while (documentPointer < text.length())
      {
        String[] nearestElement = null;
        Style nearestStyle = null;
        int startPointer = Integer.MAX_VALUE;
        // Get the start position of the nearest element with a start
        // tag length greater zero
        final Iterator<String[]> keyIterator = SyntaxHighlighter.this.highlightMap.keySet().iterator();
        final Iterator<Style> styleIterator = SyntaxHighlighter.this.highlightMap.values().iterator();
        while (keyIterator.hasNext())
        {
          final String[] nextElement = keyIterator.next();
          final Style nextStyle = styleIterator.next();
          final String startTag = nextElement[0];
          if (startTag.length() > 0)
          {
            final int indexOfstartTag = text.indexOf(startTag, documentPointer);
            if (indexOfstartTag >= 0 && indexOfstartTag < startPointer)
            {
              startPointer = indexOfstartTag;
              nearestElement = nextElement;
              nearestStyle = nextStyle;
            }
          }
        }
        // Check for break conditions
        if (nearestElement == null || nearestStyle == null)
        {
          break;
        }
        // Get the end position of the nearest element to be highlighted
        final String startTag = nearestElement[0];
        final String endTag = nearestElement[1];
        documentPointer = startPointer + startTag.length();
        int endPointer = Integer.MAX_VALUE;
        int indexOfEndTag = -1;
        if (endTag != null)
        {
          if (endTag.length() > 0)
          {
            indexOfEndTag = text.indexOf(endTag, documentPointer);
          }
          else
          {
            final int indexOfEndTag1 = text.indexOf(" ", documentPointer);
            final int indexOfEndTag2 = text.indexOf("\t", documentPointer);
            final int indexOfEndTag3 = text.indexOf("\n", documentPointer);
            if (indexOfEndTag1 >= 0 && indexOfEndTag2 >= 0)
            {
              indexOfEndTag = Math.min(indexOfEndTag1, indexOfEndTag2);
            }
            else if (indexOfEndTag1 >= 0)
            {
              indexOfEndTag = indexOfEndTag1;
            }
            else if (indexOfEndTag2 >= 0)
            {
              indexOfEndTag = indexOfEndTag2;
            }
            if (indexOfEndTag >= 0 && indexOfEndTag3 >= 0)
            {
              indexOfEndTag = Math.min(indexOfEndTag, indexOfEndTag3);
            }
            else if (indexOfEndTag3 >= 0)
            {
              indexOfEndTag = indexOfEndTag3;
            }
          }
          endPointer = indexOfEndTag + endTag.length();
        }
        else
        {
          indexOfEndTag = startPointer;
          endPointer = startPointer + startTag.length();
        }
        // Highlight the next element and set the document pointer behind the highlighted
        // element
        final int length;
        if (indexOfEndTag >= 0)
        {
          length = endPointer - startPointer;
          documentPointer = endPointer;
        }
        else
        {
          length = text.length();
          documentPointer = text.length();
        }
        this.styledDocument.setCharacterAttributes(startPointer, length, nearestStyle, false);
      }
    }
    catch (final Throwable throwable)
    {
      throwable.printStackTrace();
    }
  }
}
Comments
Locked Post
New comments cannot be posted to this locked post.
Post Details
Locked on Jul 18 2008
Added on Jun 17 2008
3 comments
179 views