CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Results 1 to 7 of 7
  1. #1
    Join Date
    Jun 2008
    Posts
    61

    Arrow It is difficult to write undo/redo capabilities for SyntaxDocument

    How to write undo/redo button for below SyntaxDocument? I read whole Chapter "Swing Undo Package" in "Core Swing Advance Programming" book but I still don't know how to do. (It's so difficult)

    I knew that I must use CompoundEdit to merge element edit and attribute edit. I can't find out how to write undo/redo for this document because of its complication.

    Do I have to use DefaultStyledDocument.AttributeUndoableEdit or AbstractDocument.ElementEdit?
    My book has an only example about undoing/redoing on JTree, it doesn't have a clear example about styled document...

    Code:
    import java.awt.*;
    import java.awt.event.*;
    import java.io.*;
    import java.net.*;
    import java.util.*;
    import javax.swing.*;
    import javax.swing.text.*;
    
    class SyntaxDocument extends DefaultStyledDocument
    {
    	private DefaultStyledDocument doc;
    	private Element rootElement;
    
    	private boolean multiLineComment;
    	private MutableAttributeSet normal;
    	private MutableAttributeSet keyword;
    	private MutableAttributeSet comment;
    	private MutableAttributeSet quote;
    
    	private HashSet keywords;
    
    	public SyntaxDocument()
    	{
    		doc = this;
    		rootElement = doc.getDefaultRootElement();
    		putProperty( DefaultEditorKit.EndOfLineStringProperty, "\n" );
    
    		normal = new SimpleAttributeSet();
    		StyleConstants.setForeground(normal, Color.black);
    
    		comment = new SimpleAttributeSet();
    		StyleConstants.setForeground(comment, Color.gray);
    		StyleConstants.setItalic(comment, true);
    
    		keyword = new SimpleAttributeSet();
    		StyleConstants.setForeground(keyword, Color.blue);
    
    		quote = new SimpleAttributeSet();
    		StyleConstants.setForeground(quote, Color.red);
    
    		keywords = new HashSet();
    		keywords.add( "abstract" );
    		keywords.add( "boolean" );
    		keywords.add( "break" );
    		keywords.add( "byte" );
    		keywords.add( "byvalue" );
    		keywords.add( "case" );
    		keywords.add( "cast" );
    		keywords.add( "catch" );
    		keywords.add( "char" );
    		keywords.add( "class" );
    		keywords.add( "const" );
    		keywords.add( "continue" );
    		keywords.add( "default" );
    		keywords.add( "do" );
    		keywords.add( "double" );
    		keywords.add( "else" );
    		keywords.add( "extends" );
    		keywords.add( "false" );
    		keywords.add( "final" );
    		keywords.add( "finally" );
    		keywords.add( "float" );
    		keywords.add( "for" );
    		keywords.add( "future" );
    		keywords.add( "generic" );
    		keywords.add( "goto" );
    		keywords.add( "if" );
    		keywords.add( "implements" );
    		keywords.add( "import" );
    		keywords.add( "inner" );
    		keywords.add( "instanceof" );
    		keywords.add( "int" );
    		keywords.add( "interface" );
    		keywords.add( "long" );
    		keywords.add( "native" );
    		keywords.add( "new" );
    		keywords.add( "null" );
    		keywords.add( "operator" );
    		keywords.add( "outer" );
    		keywords.add( "package" );
    		keywords.add( "private" );
    		keywords.add( "protected" );
    		keywords.add( "public" );
    		keywords.add( "rest" );
    		keywords.add( "return" );
    		keywords.add( "short" );
    		keywords.add( "static" );
    		keywords.add( "super" );
    		keywords.add( "switch" );
    		keywords.add( "synchronized" );
    		keywords.add( "this" );
    		keywords.add( "throw" );
    		keywords.add( "throws" );
    		keywords.add( "transient" );
    		keywords.add( "true" );
    		keywords.add( "try" );
    		keywords.add( "var" );
    		keywords.add( "void" );
    		keywords.add( "volatile" );
    		keywords.add( "while" );
    	}
    
    	/*
    	 *  Override to apply syntax highlighting after the document has been updated
    	 */
    	public void insertString(int offset, String str, AttributeSet a) throws BadLocationException
    	{
    		if (str.equals("{"))
    			str = addMatchingBrace(offset);
    
    		super.insertString(offset, str, a);
    		processChangedLines(offset, str.length());
    	}
    
    	/*
    	 *  Override to apply syntax highlighting after the document has been updated
    	 */
    	public void remove(int offset, int length) throws BadLocationException
    	{
    		super.remove(offset, length);
    		processChangedLines(offset, 0);
    	}
    
    	/*
    	 *  Determine how many lines have been changed,
    	 *  then apply highlighting to each line
    	 */
    	public void processChangedLines(int offset, int length)
    		throws BadLocationException
    	{
    		String content = doc.getText(0, doc.getLength());
    
    		//  The lines affected by the latest document update
    
    		int startLine = rootElement.getElementIndex( offset );
    		int endLine = rootElement.getElementIndex( offset + length );
    
    		//  Make sure all comment lines prior to the start line are commented
    		//  and determine if the start line is still in a multi line comment
    
    		setMultiLineComment( commentLinesBefore( content, startLine ) );
    
    		//  Do the actual highlighting
    
    		for (int i = startLine; i <= endLine; i++)
    		{
    			applyHighlighting(content, i);
    		}
    
    		//  Resolve highlighting to the next end multi line delimiter
    
    		if (isMultiLineComment())
    			commentLinesAfter(content, endLine);
    		else
    			highlightLinesAfter(content, endLine);
    	}
    
    	/*
    	 *  Highlight lines when a multi line comment is still 'open'
    	 *  (ie. matching end delimiter has not yet been encountered)
    	 */
    	private boolean commentLinesBefore(String content, int line)
    	{
    		int offset = rootElement.getElement( line ).getStartOffset();
    
    		//  Start of comment not found, nothing to do
    
    		int startDelimiter = lastIndexOf( content, getStartDelimiter(), offset - 2 );
    
    		if (startDelimiter < 0)
    			return false;
    
    		//  Matching start/end of comment found, nothing to do
    
    		int endDelimiter = indexOf( content, getEndDelimiter(), startDelimiter );
    
    		if (endDelimiter < offset & endDelimiter != -1)
    			return false;
    
    		//  End of comment not found, highlight the lines
    
    		doc.setCharacterAttributes(startDelimiter, offset - startDelimiter + 1, comment, false);
    		return true;
    	}
    
    	/*
    	 *  Highlight comment lines to matching end delimiter
    	 */
    	private void commentLinesAfter(String content, int line)
    	{
    		int offset = rootElement.getElement( line ).getEndOffset();
    
    		//  End of comment not found, nothing to do
    
    		int endDelimiter = indexOf( content, getEndDelimiter(), offset );
    
    		if (endDelimiter < 0)
    			return;
    
    		//  Matching start/end of comment found, comment the lines
    
    		int startDelimiter = lastIndexOf( content, getStartDelimiter(), endDelimiter );
    
    		if (startDelimiter < 0 || startDelimiter <= offset)
    		{
    			doc.setCharacterAttributes(offset, endDelimiter - offset + 1, comment, false);
    		}
    	}
    
    	/*
    	 *  Highlight lines to start or end delimiter
    	 */
    	private void highlightLinesAfter(String content, int line)
    		throws BadLocationException
    	{
    		int offset = rootElement.getElement( line ).getEndOffset();
    
    		//  Start/End delimiter not found, nothing to do
    
    		int startDelimiter = indexOf( content, getStartDelimiter(), offset );
    		int endDelimiter = indexOf( content, getEndDelimiter(), offset );
    
    		if (startDelimiter < 0)
    			startDelimiter = content.length();
    
    		if (endDelimiter < 0)
    			endDelimiter = content.length();
    
    		int delimiter = Math.min(startDelimiter, endDelimiter);
    
    		if (delimiter < offset)
    			return;
    
    		//	Start/End delimiter found, reapply highlighting
    
    		int endLine = rootElement.getElementIndex( delimiter );
    
    		for (int i = line + 1; i < endLine; i++)
    		{
    			Element branch = rootElement.getElement( i );
    			Element leaf = doc.getCharacterElement( branch.getStartOffset() );
    			AttributeSet as = leaf.getAttributes();
    
    			if ( as.isEqual(comment) )
    				applyHighlighting(content, i);
    		}
    	}
    
    	/*
    	 *  Parse the line to determine the appropriate highlighting
    	 */
    	private void applyHighlighting(String content, int line)
    		throws BadLocationException
    	{
    		int startOffset = rootElement.getElement( line ).getStartOffset();
    		int endOffset = rootElement.getElement( line ).getEndOffset() - 1;
    
    		int lineLength = endOffset - startOffset;
    		int contentLength = content.length();
    
    		if (endOffset >= contentLength)
    			endOffset = contentLength - 1;
    
    		//  check for multi line comments
    		//  (always set the comment attribute for the entire line)
    
    		if (endingMultiLineComment(content, startOffset, endOffset)
    		||  isMultiLineComment()
    		||  startingMultiLineComment(content, startOffset, endOffset) )
    		{
    			doc.setCharacterAttributes(startOffset, endOffset - startOffset + 1, comment, false);
    			return;
    		}
    
    		//  set normal attributes for the line
    
    		doc.setCharacterAttributes(startOffset, lineLength, normal, true);
    
    		//  check for single line comment
    
    		int index = content.indexOf(getSingleLineDelimiter(), startOffset);
    
    		if ( (index > -1) && (index < endOffset) )
    		{
    			doc.setCharacterAttributes(index, endOffset - index + 1, comment, false);
    			endOffset = index - 1;
    		}
    
    		//  check for tokens
    
    		checkForTokens(content, startOffset, endOffset);
    	}
    
    	/*
    	 *  Does this line contain the start delimiter
    	 */
    	private boolean startingMultiLineComment(String content, int startOffset, int endOffset)
    		throws BadLocationException
    	{
    		int index = indexOf( content, getStartDelimiter(), startOffset );
    
    		if ( (index < 0) || (index > endOffset) )
    			return false;
    		else
    		{
    			setMultiLineComment( true );
    			return true;
    		}
    	}
    
    	/*
    	 *  Does this line contain the end delimiter
    	 */
    	private boolean endingMultiLineComment(String content, int startOffset, int endOffset)
    		throws BadLocationException
    	{
    		int index = indexOf( content, getEndDelimiter(), startOffset );
    
    		if ( (index < 0) || (index > endOffset) )
    			return false;
    		else
    		{
    			setMultiLineComment( false );
    			return true;
    		}
    	}
    
    	/*
    	 *  We have found a start delimiter
    	 *  and are still searching for the end delimiter
    	 */
    	private boolean isMultiLineComment()
    	{
    		return multiLineComment;
    	}
    
    	private void setMultiLineComment(boolean value)
    	{
    		multiLineComment = value;
    	}
    
    	/*
    	 *	Parse the line for tokens to highlight
    	 */
    	private void checkForTokens(String content, int startOffset, int endOffset)
    	{
    		while (startOffset <= endOffset)
    		{
    			//  skip the delimiters to find the start of a new token
    
    			while ( isDelimiter( content.substring(startOffset, startOffset + 1) ) )
    			{
    				if (startOffset < endOffset)
    					startOffset++;
    				else
    					return;
    			}
    
    			//  Extract and process the entire token
    
    			if ( isQuoteDelimiter( content.substring(startOffset, startOffset + 1) ) )
    				startOffset = getQuoteToken(content, startOffset, endOffset);
    			else
    				startOffset = getOtherToken(content, startOffset, endOffset);
    		}
    	}
    
    	/*
    	 *
    	 */
    	private int getQuoteToken(String content, int startOffset, int endOffset)
    	{
    		String quoteDelimiter = content.substring(startOffset, startOffset + 1);
    		String escapeString = getEscapeString(quoteDelimiter);
    
    		int index;
    		int endOfQuote = startOffset;
    
    		//  skip over the escape quotes in this quote
    
    		index = content.indexOf(escapeString, endOfQuote + 1);
    
    		while ( (index > -1) && (index < endOffset) )
    		{
    			endOfQuote = index + 1;
    			index = content.indexOf(escapeString, endOfQuote);
    		}
    
    		// now find the matching delimiter
    
    		index = content.indexOf(quoteDelimiter, endOfQuote + 1);
    
    		if ( (index < 0) || (index > endOffset) )
    			endOfQuote = endOffset;
    		else
    			endOfQuote = index;
    
    		doc.setCharacterAttributes(startOffset, endOfQuote - startOffset + 1, quote, false);
    
    		return endOfQuote + 1;
    	}
    
    	/*
    	 *
    	 */
    	private int getOtherToken(String content, int startOffset, int endOffset)
    	{
    		int endOfToken = startOffset + 1;
    
    		while ( endOfToken <= endOffset )
    		{
    			if ( isDelimiter( content.substring(endOfToken, endOfToken + 1) ) )
    				break;
    
    			endOfToken++;
    		}
    
    		String token = content.substring(startOffset, endOfToken);
    
    		if ( isKeyword( token ) )
    		{
    			doc.setCharacterAttributes(startOffset, endOfToken - startOffset, keyword, false);
    		}
    
    		return endOfToken + 1;
    	}
    
    	/*
    	 *  Assume the needle will the found at the start/end of the line
    	 */
    	private int indexOf(String content, String needle, int offset)
    	{
    		int index;
    
    		while ( (index = content.indexOf(needle, offset)) != -1 )
    		{
    			String text = getLine( content, index ).trim();
    
    			if (text.startsWith(needle) || text.endsWith(needle))
    				break;
    			else
    				offset = index + 1;
    		}
    
    		return index;
    	}
    
    	/*
    	 *  Assume the needle will the found at the start/end of the line
    	 */
    	private int lastIndexOf(String content, String needle, int offset)
    	{
    		int index;
    
    		while ( (index = content.lastIndexOf(needle, offset)) != -1 )
    		{
    			String text = getLine( content, index ).trim();
    
    			if (text.startsWith(needle) || text.endsWith(needle))
    				break;
    			else
    				offset = index - 1;
    		}
    
    		return index;
    	}
    
    	private String getLine(String content, int offset)
    	{
    		int line = rootElement.getElementIndex( offset );
    		Element lineElement = rootElement.getElement( line );
    		int start = lineElement.getStartOffset();
    		int end = lineElement.getEndOffset();
    		return content.substring(start, end - 1);
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected boolean isDelimiter(String character)
    	{
    		String operands = ";:{}()[]+-/&#37;<=>!&|^~*";
    
    		if (Character.isWhitespace( character.charAt(0) ) ||
    			operands.indexOf(character) != -1 )
    			return true;
    		else
    			return false;
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected boolean isQuoteDelimiter(String character)
    	{
    		String quoteDelimiters = "\"'";
    
    		if (quoteDelimiters.indexOf(character) < 0)
    			return false;
    		else
    			return true;
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected boolean isKeyword(String token)
    	{
    		return keywords.contains( token );
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected String getStartDelimiter()
    	{
    		return "/*";
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected String getEndDelimiter()
    	{
    		return "*/";
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected String getSingleLineDelimiter()
    	{
    		return "//";
    	}
    
    	/*
    	 *  Override for other languages
    	 */
    	protected String getEscapeString(String quoteDelimiter)
    	{
    		return "\\" + quoteDelimiter;
    	}
    
    	/*
    	 *
    	 */
    	protected String addMatchingBrace(int offset) throws BadLocationException
    	{
    		StringBuffer whiteSpace = new StringBuffer();
    		int line = rootElement.getElementIndex( offset );
    		int i = rootElement.getElement(line).getStartOffset();
    
    		while (true)
    		{
    			String temp = doc.getText(i, 1);
    
    			if (temp.equals(" ") || temp.equals("\t"))
    			{
    				whiteSpace.append(temp);
    				i++;
    			}
    			else
    				break;
    		}
    
    		return "{\n" + whiteSpace.toString() + "\t\n" + whiteSpace.toString() + "}";
    	}
    
    
    	public static void main(String a[])
    	{
    
    		EditorKit editorKit = new StyledEditorKit()
    		{
    			public Document createDefaultDocument()
    			{
    				return new SyntaxDocument();
    			}
    		};
    
    		final JEditorPane edit = new JEditorPane();
    		edit.setEditorKitForContentType("text/java", editorKit);
    		edit.setContentType("text/java");
    //		edit.setEditorKit(new StyledEditorKit());
    //		edit.setDocument(new SyntaxDocument());
    
    		JButton button = new JButton("Load SyntaxDocument.java");
    		button.addActionListener( new ActionListener()
    		{
    			public void actionPerformed(ActionEvent e)
    			{
    				try
    				{
    					FileInputStream fis = new FileInputStream( "SyntaxDocument.java" );
    //					FileInputStream fis = new FileInputStream( "C:\\Java\\jdk1.4.1\\src\\javax\\swing\\JComponent.java" );
    					edit.read( fis, null );
    					edit.requestFocus();
    				}
    				catch(Exception e2) {}
    			}
    		});
    
    		JFrame frame = new JFrame("Syntax Highlighting");
    		frame.getContentPane().add( new JScrollPane(edit) );
    		frame.getContentPane().add(button, BorderLayout.SOUTH);
    		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    		frame.setSize(800,300);
    		frame.setVisible(true);
    	}
    }
    Last edited by Emerald214; December 22nd, 2009 at 02:13 AM.

  2. #2
    Join Date
    Jun 2008
    Posts
    61

    Re: It is difficult to write undo/redo capabilities for SyntaxDocument

    The problem is my book doesn't have example code about undoing/redoing on styled document. I googled but there aren't any tutorials about it.

  3. #3
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: It is difficult to write undo/redo capabilities for SyntaxDocument

    Does this article help?

  4. #4
    Join Date
    Jun 2008
    Posts
    61

    Re: It is difficult to write undo/redo capabilities for SyntaxDocument

    Thank you but it doesn't help me much. It is an example about undoing/redoing on plain text document.
    Last edited by Emerald214; December 24th, 2009 at 02:29 AM.

  5. #5
    Join Date
    May 2006
    Location
    UK
    Posts
    4,473

    Re: It is difficult to write undo/redo capabilities for SyntaxDocument

    What it says is:
    For PlainDocuments this involves keeping track of text insertions and removals, as well as any structural changes. For StyledDocuments, however, this involves keeping track of a much larger group of changes. Fortunately this work has been built into these document models for us
    Now I've never tried doing this but, to me, the article suggests the functionality is built into the model and so the example given should work for either document model. Have you tried it?

  6. #6
    Join Date
    Jun 2008
    Posts
    61

    Arrow Re: It is difficult to write undo/redo capabilities for SyntaxDocument

    That example code totally doesn't show how to build undo/redo funtionality in styled document because it uses JTextArea. (document of JTextArea is plain)
    To undo in styled document, must use CompoundEdit...

    This is exactly what I am finding:
    http://stackoverflow.com/questions/1...-letter-a-time
    (I don't know why noone answers me )

  7. #7
    dlorde is offline Elite Member Power Poster
    Join Date
    Aug 1999
    Location
    UK
    Posts
    10,163

    Re: It is difficult to write undo/redo capabilities for SyntaxDocument

    Quote Originally Posted by Emerald214 View Post
    (I don't know why noone answers me )
    Probably because no-one who knows the answer has read your question. Have you tried the Sun Java forums?

    The first 90% of the code accounts for the first 90% of the development time. The remaining 10% of the code accounts for the other 90% of the development time...
    T. Cargill
    Please use &#91;CODE]...your code here...&#91;/CODE] tags when posting code. If you get an error, please post the full error message and stack trace, if present.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  





Click Here to Expand Forum to Full Width

Featured