Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: novaworx/textpane/SyntaxTextPane.java


1   /*
2   Novaworx Development Environment
3   Copyright (C) 2000-2003 Mark Soderquist
4   Portions Copyright (C) 1998-2001 Slava Pestov
5   Portions Copyright (C) 1999-2000 Mike Dillon
6   
7   This program is free software; you can redistribute it and/or modify
8   it under the terms of the GNU General Public License as published by
9   the Free Software Foundation; either version 2 of the License, or
10  (at your option) any later version.
11  
12  This program is distributed in the hope that it will be useful,
13  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  GNU General Public License for more details.
16  
17  You should have received a copy of the GNU General Public License
18  along with this program; if not, write to:
19  
20  Free Software Foundation, Inc.
21  59 Temple Place, Suite 330
22  Boston, MA 02111-1307 USA
23  */
24  
25  package novaworx.textpane;
26  
27  import java.awt.*;
28  import java.awt.event.*;
29  import java.awt.font.*;
30  import java.lang.reflect.*;
31  import java.util.regex.*;
32  import javax.swing.*;
33  import javax.swing.event.*;
34  import javax.swing.plaf.*;
35  import javax.swing.text.*;
36  import javax.swing.undo.*;
37  import novaworx.syntax.*;
38  import cosmoworx.log.*;
39  
40  /**
41  <p>The <code>SyntaxTextPane</code> is a powerful set of classes for editing primarily
42  text.  It may be adapted to handle editing of binary files at a future date.</p>
43  
44  <p>The <code>SyntaxTextPane</code> was derived from two versions of the program
45  <a href="http://www.jedit.org">jEdit</a> written by Slava Pestov.</p>
46  
47  @author Slava Pestov
48  @author Mark Soderquist
49  @since Novaworx 1.0
50  */
51  public class SyntaxTextPane extends JEditorPane {
52  
53    /**
54    The default editor kit for this text component.
55    */
56    public static final EditorKit EDITOR_KIT = new SyntaxEditorKit();
57  
58    public static final String STYLES_ATTRIBUTE = "colors";
59  
60    private static boolean mbKeymapInitialized;
61  
62    private String msSyntax;
63  
64    private boolean mbAntiAlias;
65  
66    private Color moLineHighlightColor;
67  
68    private Object moLineHighlightTag;
69  
70    private int miBracketLine = -1;
71  
72    private int miOtherBracketLine = -1;
73  
74    private Color moBracketHighlightColor;
75  
76    private Object moBracketHighlightTag;
77  
78    private Object moOtherBracketHighlightTag;
79  
80    private Object moBlockCaretHighlightTag;
81  
82    private int miTabSize = 2;
83  
84    private int miElectricLines;
85  
86    private boolean mbBlock;
87  
88    private boolean mbOverwrite;
89  
90    private boolean mbBlockSelect;
91  
92    private int miLastLine = -1;
93  
94    private int miLastBracketPosition = -1;
95  
96    private int miLastOtherBracketPosition = -1;
97  
98    private double mdScrollToPercentFromTop = 0.3;
99  
100   private static Cursor moNoCursor;
101 
102   private static Cursor moTextCursor;
103 
104   /**
105   Create a new <code>SyntaxTextPane</code> component without syntax highlighting by default.
106   */
107   public SyntaxTextPane() {
108     Toolkit oToolkit = Toolkit.getDefaultToolkit();
109     byte[] aData = { 0 };
110 
111     setBorder(null);
112 
113     // Setup the cursor.
114     moNoCursor = oToolkit.createCustomCursor( oToolkit.createImage( aData ), new Point( 0, 0 ), "cursor.none" );
115     moTextCursor = Cursor.getPredefinedCursor( Cursor.TEXT_CURSOR );
116     setCursor( moTextCursor );
117     addMouseMotionListener( new MouseMotionHandler() );
118 
119     // Setup default colors.
120     setSyntaxStyles( getDefaultSyntaxStyles() );
121     Color oSelectionColor = UIManager.getColor( "List.selectionBackground" );
122     Color oNewSelectionColor = new Color( oSelectionColor.getRed(), oSelectionColor.getGreen(), oSelectionColor.getBlue(), 128 );
123     setSelectionColor( oNewSelectionColor );
124     moLineHighlightColor = new Color( 128, 128, 128, 64 );
125     moBracketHighlightColor = new Color( 255, 128, 128, 128 );
126 
127     // FIX There is a new way of handling keymaps in Java 1.4.0.
128     // Initialize the keymap.
129     if( !mbKeymapInitialized ) {
130       Keymap oMap = JTextComponent.getKeymap( JTextComponent.DEFAULT_KEYMAP );
131       oMap.setDefaultAction( new DefaultKeyTypedAction() );
132       oMap.addActionForKeyStroke( KeyStroke.getKeyStroke( KeyEvent.VK_INSERT, 0 ), new InsertKeyAction() );
133       mbKeymapInitialized = true;
134     }
135 
136     // Set up actions.
137     ActionMap oActionMap = getActionMap();
138 
139     // Display the action keys in the map.
140     //Object[] aKeys = oActionMap.allKeys();
141     //for( int iIndex = 0; iIndex < aKeys.length; iIndex++ ) {
142     //  System.out.println( "Action key: " + aKeys[ iIndex ].toString() );
143     //}
144 
145     oActionMap.put( "undo", new UndoAction() );
146     oActionMap.put( "redo", new RedoAction() );
147     oActionMap.put( "indent", new IndentAction() );
148     oActionMap.put( "unindent", new UnindentAction() );
149 
150     // Setup the caret.
151     EditorCaret oCaret = new EditorCaret();
152     setCaret( oCaret );
153     setCaretColor( Color.red );
154     setElectricBorder( 3 );
155   }
156 
157   /**
158   Returns the default editor kit for this text component.
159   */
160   public EditorKit createDefaultEditorKit() {
161     return EDITOR_KIT;
162   }
163 
164   /**
165   Set the anti-alias flag.
166   */
167   public void setAntiAlias( boolean abAntiAlias ) {
168     mbAntiAlias = abAntiAlias;
169     repaint();
170   }
171 
172   /**
173   Get the anit-alias flag.
174   */
175   public boolean isAntiAlias() {
176     return mbAntiAlias;
177   }
178 
179   /**
180   Sets the currently highlighted line.
181   @param aiLineStart The start offset of the line in the document.
182   @param aiLineEnd The end offset of the line in the document.
183   */
184 
185   public void setHighlightedLine( int aiLineStart, int aiLineEnd ) {
186     if( moLineHighlightTag == null ) return;
187 
188     try {
189       getHighlighter().changeHighlight( moLineHighlightTag, aiLineStart, aiLineEnd);
190     } catch( BadLocationException aoException ) {
191       Log.write( Log.ERROR, aoException );
192     }
193   }
194 
195   /**
196   Sets the line highlight color.
197   @param aoColor The line highlight color.
198   */
199 
200   public void setLineHighlightColor( Color aoColor ) {
201     moLineHighlightColor = aoColor;
202   }
203 
204   /**
205   Returns the line highlight color.
206   */
207 
208   public Color getLineHighlightColor() {
209     return moLineHighlightColor;
210   }
211 
212   /**
213   Sets the current line highlighting feature.
214   @param abLineHighlight True if the current line should be
215   highlighted, false otherwise.
216   */
217   public void setLineHighlight( boolean abLineHighlight ) {
218     if( moLineHighlightTag != null ) {
219       if( abLineHighlight) {
220         return;
221       } else {
222         getHighlighter().removeHighlight( moLineHighlightTag );
223         moLineHighlightTag = null;
224       }
225     } else if( abLineHighlight ) {
226       try {
227         moLineHighlightTag = getHighlighter().addHighlight( 0, 0, new CurrentLineHighlighter() );
228       } catch(BadLocationException aoException ) {
229         Log.write( Log.ERROR, aoException );
230       }
231     }
232   }
233 
234   /**
235   Returns true if current line highlighting is enabled, false
236   otherwise.
237   */
238   public boolean getLineHighlight() {
239     return moLineHighlightTag != null;
240   }
241 
242   /**
243   Sets the highlighted bracket.
244   @param aiBracketPosition The offset of the bracket in the document.
245   */
246   public void setHighlightedBracket( int aiBracketPosition ) {
247     if( moBracketHighlightTag == null ) return;
248 
249     if( aiBracketPosition == miLastBracketPosition ) return;
250 
251     miLastBracketPosition = aiBracketPosition;
252 
253     try
254     {
255       if( aiBracketPosition == -1) {
256         getHighlighter().changeHighlight( moBracketHighlightTag, 0, 0 );
257       } else {
258         getHighlighter().changeHighlight( moBracketHighlightTag, aiBracketPosition, aiBracketPosition + 1 );
259       }
260     } catch( BadLocationException aoException ) {
261       Log.write( Log.ERROR, aoException );
262     }
263   }
264 
265   /**
266   Sets the other highlighted bracket.
267   @param aiBracketPosition The offset of the other bracket in the document.
268   */
269   public void setOtherHighlightedBracket( int aiOtherBracketPosition ) {
270     if( moOtherBracketHighlightTag == null ) return;
271 
272     if( aiOtherBracketPosition == miLastOtherBracketPosition ) return;
273 
274     miLastOtherBracketPosition = aiOtherBracketPosition;
275 
276     try
277     {
278       if( aiOtherBracketPosition == -1) {
279         getHighlighter().changeHighlight( moOtherBracketHighlightTag, 0, 0 );
280       } else {
281         getHighlighter().changeHighlight( moOtherBracketHighlightTag, aiOtherBracketPosition, aiOtherBracketPosition + 1 );
282       }
283     } catch( BadLocationException aoException ) {
284       Log.write( Log.ERROR, aoException );
285     }
286   }
287 
288   /**
289   Sets the bracket highlight color.
290   @param aoColor The bracket highlight color.
291   */
292 
293   public void setBracketHighlightColor( Color aoColor ) {
294     moBracketHighlightColor = aoColor;
295   }
296 
297   /**
298   Returns the bracket highlight color.
299   */
300 
301   public Color getBracketHighlightColor() {
302     return moBracketHighlightColor;
303   }
304 
305   /**
306   Sets the bracket highlighting feature.
307   @param abBracketHighlight True if matching brackets should be highlighted, false otherwise.
308   */
309 
310   public void setBracketHighlight( boolean abBracketHighlight ) {
311     if( moBracketHighlightTag != null) {
312       if( abBracketHighlight ) {
313         return;
314       } else {
315         getHighlighter().removeHighlight( moBracketHighlightTag );
316         moBracketHighlightTag = null;
317       }
318     } else if( abBracketHighlight ) {
319       try {
320         moBracketHighlightTag = getHighlighter().addHighlight( 0, 0, new BracketHighlighter() );
321       } catch( BadLocationException aoException ) {
322         Log.write( Log.ERROR, aoException );
323       }
324     }
325 
326     if( moOtherBracketHighlightTag != null) {
327       if( abBracketHighlight ) {
328         return;
329       } else {
330         getHighlighter().removeHighlight( moOtherBracketHighlightTag );
331         moOtherBracketHighlightTag = null;
332       }
333     } else if( abBracketHighlight ) {
334       try {
335         moOtherBracketHighlightTag = getHighlighter().addHighlight( 0, 0, new BracketHighlighter() );
336       } catch( BadLocationException aoException ) {
337         Log.write( Log.ERROR, aoException );
338       }
339     }
340   }
341 
342   /**
343   Returns true if bracket highlighting is enabled, false otherwise.
344   */
345 
346   public boolean getBracketHighlight() {
347     return moBracketHighlightTag != null & moOtherBracketHighlightTag != null;
348   }
349 
350   /**
351   Sets the number of lines from the top or bottom of the
352   text area from which autoscroll begins.
353   @param aiLines The number of lines
354   */
355 
356   public void setElectricBorder( int aiLines ) {
357     miElectricLines = aiLines;
358   }
359 
360   /**
361   Returns the number of lines from the top or bottom of the
362   text area from which autoscrolling begins.
363   */
364 
365   public int getElectricBorder() {
366     return miElectricLines;
367   }
368 
369   /**
370   Sets the block caret.
371   @param abBlock True if a block caret should be drawn, false otherwise
372   */
373 
374   public void setBlockCaret( boolean abBlockCaret ) {
375     mbBlock = abBlockCaret;
376 
377     if( moBlockCaretHighlightTag != null ) {
378       if( abBlockCaret & !mbOverwrite ) {
379         return;
380       } else {
381         getHighlighter().removeHighlight( moBlockCaretHighlightTag );
382         moLineHighlightTag = null;
383       }
384     } else if( abBlockCaret & !mbOverwrite ) {
385       try {
386         moBlockCaretHighlightTag = getHighlighter().addHighlight( 0, 0, new BlockCaretHighlighter() );
387       } catch(BadLocationException aoException ) {
388         Log.write( Log.ERROR, aoException );
389       }
390     }
391   }
392 
393   /**
394   Returns true if a block caret is enabled, false otherwise.
395   */
396 
397   public boolean isBlockCaret() {
398     return mbBlock;
399   }
400 
401   /**
402   Sets the block caret highlight position.
403   @param aiBracketPosition The offset of the other bracket in the document.
404   */
405   public void setBlockCaretHighlight( int aiPosition ) {
406     if( moBlockCaretHighlightTag == null ) return;
407 
408     try {
409       if( aiPosition == -1 ) {
410         getHighlighter().changeHighlight( moBlockCaretHighlightTag, 0, 0 );
411       } else {
412         getHighlighter().changeHighlight( moBlockCaretHighlightTag, aiPosition, aiPosition + 1 );
413       }
414     } catch( BadLocationException aoException ) {
415       Log.write( Log.ERROR, aoException );
416     }
417   }
418 
419   /**
420   Set the caret on time.
421   */
422   public void setCaretOnDelay( int aiDelay ) {
423     ((EditorCaret)getCaret()).setBlinkOnDelay( aiDelay );
424   }
425 
426   /**
427   Get the caret on time.
428   */
429   public int getCaretOnDelay() {
430     return ((EditorCaret)getCaret()).getBlinkOnDelay();
431   }
432 
433   /**
434   Set the caret off time.
435   */
436   public void setCaretOffDelay( int aiDelay ) {
437     ((EditorCaret)getCaret()).setBlinkOffDelay( aiDelay );
438   }
439 
440   /**
441   Get the caret off time.
442   */
443   public int getCaretOffDelay() {
444     return ((EditorCaret)getCaret()).getBlinkOffDelay();
445   }
446 
447   /**
448   Set the tab size for the editor.
449   */
450   public void setTabSize( int aiTabSize ) {
451     miTabSize = aiTabSize;
452     getSyntaxDocument().setTabSize( aiTabSize );
453 
454     revalidate();
455     repaint();
456   }
457 
458   /**
459   Get the tab size for the editor.
460   */
461   public int getTabSize() {
462     return miTabSize;
463   }
464 
465   /**
466   Sets the overwrite syntax.
467   @param abOverwrite True if newly inserted characters should
468   overwrite existing ones, false if they should be inserted.
469   */
470 
471   public void setOverwrite( boolean abOverwrite ) {
472     mbOverwrite = abOverwrite;
473   }
474 
475   /**
476   Returns true if overwrite syntax is enabled, false otherwise.
477   */
478 
479   public boolean getOverwrite() {
480     return mbOverwrite;
481   }
482 
483   /**
484   Sets the overwrite syntax flag to the opposite of it's
485   current value.
486   */
487 
488   public boolean toggleOverwrite() {
489     return mbOverwrite = !mbOverwrite;
490   }
491 
492   /**
493   Set the block select flag.
494   */
495   public void setBlockSelect( boolean abBlockSelect ) {
496     mbBlockSelect = abBlockSelect;
497   }
498 
499   /**
500   Get the block select flag.
501   */
502   public boolean isBlockSelect() {
503     return mbBlockSelect;
504   }
505 
506   /**
507   Toggle the block select flag.
508   */
509   public boolean toggleBlockSelect() {
510     return mbBlockSelect = !mbBlockSelect;
511   }
512 
513   /**
514   A unit is the height of one line of text.
515   */
516   public int getScrollableUnitIncrement( Rectangle aoVisibleArea, int aiOrientation, int aiDirection) {
517     return getLineHeight();
518   }
519 
520   /**
521   Updates the highlighters.
522   */
523   public void updateHighlighters() {
524     Document oDocument = getDocument();
525     int iCaretPosition = getCaretPosition();
526     Element oMap = oDocument.getDefaultRootElement();
527     int iLineNumber = oMap.getElementIndex( iCaretPosition );
528     Element oLineElement = oMap.getElement( iLineNumber );
529     int iStart = oLineElement.getStartOffset();
530     int iEnd = oLineElement.getEndOffset();
531 
532     // Set the caret highlight.
533     setBlockCaretHighlight( iCaretPosition );
534 
535     // Set the current line highlight.
536     if( getSelectionStart() == getSelectionEnd() ) {
537       if( iLineNumber != miLastLine ) {
538         setHighlightedLine( iStart, iEnd);
539         miLastLine = iLineNumber;
540       }
541     }
542 
543     // Set the bracket highlight.
544     try {
545       int iOffset = -1;
546       char cBracket = ' ';
547       Segment oSegment = new Segment();
548 
549       if( iCaretPosition >= 0 && iOffset < 0 ) {
550         oDocument.getText( iCaretPosition, 1, oSegment);
551         cBracket = oSegment.array[ oSegment.offset ];
552         switch( cBracket ) {
553           case '(' : case ')' :
554           case '{' : case '}' :
555           case '[' : case ']' :
556             iOffset = iCaretPosition;
557         }
558       }
559 
560       if( iCaretPosition > 0 && iOffset < 0 ) {
561         oDocument.getText( iCaretPosition - 1, 1, oSegment);
562         cBracket = oSegment.array[ oSegment.offset ];
563         switch( cBracket ) {
564           case '(' : case ')' :
565           case '{' : case '}' :
566           case '[' : case ']' :
567             iOffset = iCaretPosition - 1;
568         }
569       }
570 
571       int iOtherOffset = -1;
572       if( iOffset >= 0 ) {
573         switch( cBracket ) {
574         case '(':
575           iOtherOffset = SyntaxUtilities.locateBracketForward( oDocument, iOffset, '(', ')' );
576           break;
577         case ')':
578           iOtherOffset = SyntaxUtilities.locateBracketBackward( oDocument, iOffset, '(', ')');
579           break;
580         case '[':
581           iOtherOffset = SyntaxUtilities.locateBracketForward( oDocument, iOffset, '[', ']');
582           break;
583         case ']':
584           iOtherOffset = SyntaxUtilities.locateBracketBackward( oDocument, iOffset, '[', ']');
585           break;
586         case '{':
587           iOtherOffset = SyntaxUtilities.locateBracketForward( oDocument, iOffset, '{', '}');
588           break;
589         case '}':
590           iOtherOffset = SyntaxUtilities.locateBracketBackward( oDocument, iOffset, '{', '}');
591           break;
592         }
593 
594         // Set the highlight on the bracket if the other bracket was found.
595         if( iOtherOffset >= 0 ) {
596           setHighlightedBracket( iOffset );
597           setOtherHighlightedBracket( iOtherOffset );
598           int iBracketLine = getLineIndexForOffset( iOffset );
599           int iOtherBracketLine = getLineIndexForOffset( iOtherOffset );
600           miBracketLine = Math.min( iBracketLine, iOtherBracketLine );
601           miOtherBracketLine = Math.max( iBracketLine, iOtherBracketLine );
602         } else {
603           setHighlightedBracket( -1 );
604           setOtherHighlightedBracket( -1 );
605           miBracketLine = -1;
606           miOtherBracketLine = -1;
607         }
608       } else {
609         setHighlightedBracket( -1 );
610         setOtherHighlightedBracket( -1 );
611         miBracketLine = -1;
612         miOtherBracketLine = -1;
613       }
614     } catch(BadLocationException aoException ) {
615       Log.write( Log.ERROR, aoException );
616     }
617   }
618 
619   /**
620   Get the current row.
621   */
622   public int getCurrentRow() {
623     return getCaretLineIndex() + 1;
624   }
625 
626   /**
627   Get the current column.
628   */
629   public int getCurrentColumn() {
630     return ( getCaret().getDot() - getStartOffsetForLineIndex( getCaretLineIndex() ) ) + 1;
631   }
632 
633   /**
634   Get the line index of the upper bracket.
635   */
636   public int getBracketLineIndex() {
637     return miBracketLine;
638   }
639 
640   /**
641   Get the line index of the lower bracket.
642   */
643   public int getOtherBracketLineIndex() {
644     return miOtherBracketLine;
645   }
646 
647   /**
648   Get the line index of the caret.
649   */
650   public int getCaretLineIndex() {
651     int iDot = getCaret().getDot();
652     return getLineIndexForOffset( iDot );
653   }
654 
655   /**
656   Get the number of lines in the document.
657   */
658   public int getLineCount() {
659     return getSyntaxDocument().getLineCount();
660   }
661 
662   /**
663   Get the line index for the specified offset.
664   */
665   public int getLineIndexForOffset( int aiOffset ) {
666     return getSyntaxDocument().getLineIndexForOffset( aiOffset );
667   }
668 
669   /**
670   Get the start offset for the specified line index.
671   */
672   public int getStartOffsetForLineIndex( int aiIndex ) {
673     return getSyntaxDocument().getStartOffsetForLineIndex( aiIndex );
674   }
675 
676   /**
677   Get the end offset for the specified line index.
678   */
679   public int getEndOffsetForLineIndex( int aiIndex ) {
680     return getSyntaxDocument().getEndOffsetForLineIndex( aiIndex );
681   }
682 
683   /**
684   Sets the document edited by this text area. This method
685   makes sure that it implements the <code>SyntaxDocument</code> interface.
686   @param aoDocument The document.
687   @see novaworx.textpane.SyntaxDocument
688   */
689 
690   public void setDocument( Document aoDocument ) {
691     if( !( aoDocument instanceof SyntaxDocument ) ) throw new IllegalArgumentException( "Document is not an instance of SyntaxDocument" );
692 
693     invalidate();
694     super.setDocument( aoDocument );
695     ((SyntaxDocument)aoDocument).setTabSize( miTabSize );
696   }
697 
698   /**
699   Returns the text area's document, typecast to a
700   <code>SyntaxDocument</code>.
701   @see novaworx.textpane.SyntaxDocument
702   */
703 
704   public SyntaxDocument getSyntaxDocument() {
705     return (SyntaxDocument)getDocument();
706   }
707 
708   /**
709   Sets the syntax that is to be used to split lines of
710   this document up into tokens.
711   @param asSyntax The name of the syntax.
712   */
713   public void setSyntax( String asSyntax ) {
714     getSyntaxDocument().setSyntax( SyntaxFactory.getSyntax( asSyntax ) );
715   }
716 
717   /**
718   Sets the syntax that is to be used to split lines of
719   this document up into tokens.
720   @param aoSyntax The syntax.
721   */
722   public void setSyntax( Syntax aoSyntax ) {
723     getSyntaxDocument().setSyntax( aoSyntax );
724   }
725 
726   /**
727   Returns the syntax that is to be used to split lines
728   of this document up into tokens.
729   @return The syntax.
730   */
731   public Syntax getSyntax() {
732     return getSyntaxDocument().getSyntax();
733   }
734 
735   /**
736   Get the name of the syntax being used.
737   @return The name of the syntax.
738   */
739   public String getSyntaxName() {
740     Syntax oSyntax = getSyntax();
741     String sName = oSyntax == null ? "text" : oSyntax.getName();
742     return sName;
743   }
744 
745   /**
746   Sets the style array.
747   @param aaStyles The new style list.
748   */
749   public void setSyntaxStyles( SyntaxStyle[] aaStyles ) {
750     putClientProperty( STYLES_ATTRIBUTE, aaStyles );
751   }
752 
753   /**
754   Returns the style array.
755   @return The style list.
756   */
757   public SyntaxStyle[] getSyntaxStyles() {
758     return (SyntaxStyle[])getClientProperty( STYLES_ATTRIBUTE );
759   }
760 
761   public void doElectricScroll( Rectangle aoArea ) {
762     FontRenderContext oContext = new FontRenderContext( null, false, false );
763     LineMetrics oMetrics = getFont().getLineMetrics( "This is a test.", oContext );
764 
765     int iHeight = (int)oMetrics.getHeight();
766     int iY = Math.max( 0, aoArea.y - iHeight * miElectricLines );
767     int iLines = iHeight * miElectricLines * 2;
768     if( iY + iLines + aoArea.height <= getHeight() ) {
769       aoArea.y = iY;
770       aoArea.height += iLines;
771     }
772     scrollRectToVisible( aoArea );
773   }
774 
775   public int getLineHeight() {
776     return getFontMetrics( getFont() ).getHeight();
777   }
778 
779   /**
780   Paint the component.
781   */
782   public void paint( Graphics aoGraphics ) {
783     if( mbAntiAlias ) {
784       Graphics2D oGraphics = (Graphics2D)aoGraphics;
785       oGraphics.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
786       //oGraphics.setRenderingHint( RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON );
787     }
788     super.paint( aoGraphics );
789   }
790 
791   /**
792   Set the foreground color.
793   */
794   public void setForeground( Color aoColor ) {
795     super.setForeground( aoColor );
796     SyntaxStyle[] aStyles = getSyntaxStyles();
797     if( aStyles != null ) {
798       SyntaxStyle oStyle = aStyles[ Token.NULL ];
799       if( oStyle != null ) oStyle.setForeground( aoColor );
800     }
801   }
802 
803   /**
804   Set the background color.
805   */
806   public void setBackground( Color aoColor ) {
807     super.setBackground( aoColor );
808     SyntaxStyle[] aStyles = getSyntaxStyles();
809     if( aStyles != null ) {
810       SyntaxStyle oStyle = aStyles[ Token.NULL ];
811       if( oStyle != null ) oStyle.setBackground( aoColor );
812     }
813   }
814 
815   /**
816   Returns the default style table. This can be passed to
817   <code>SyntaxTextPane.setStyles()</code>.
818   */
819 
820   public static SyntaxStyle[] getDefaultSyntaxStyles() {
821     SyntaxStyle[] aStyles = new SyntaxStyle[ Token.ID_COUNT ];
822 
823     aStyles[ Token.NULL ] = new SyntaxStyle( new Color( 0, 0, 0 ) );
824     aStyles[ Token.COMMENT1 ] = new SyntaxStyle( new Color( 128, 128, 128 ) );
825     aStyles[ Token.COMMENT2 ] = new SyntaxStyle( new Color( 128, 128, 128 ) );
826     aStyles[ Token.COMMENT3 ] = new SyntaxStyle( new Color( 0, 0, 160 ) );
827     aStyles[ Token.DIGIT ] = new SyntaxStyle( new Color( 0, 160, 0) );
828     aStyles[ Token.LITERAL1 ] = new SyntaxStyle( new Color( 64, 128, 255 ) );
829     aStyles[ Token.LITERAL2 ] = new SyntaxStyle( new Color( 96, 96, 255 ) );
830     aStyles[ Token.KEYWORD1 ] = new SyntaxStyle( new Color( 0, 96, 160 ) );
831     aStyles[ Token.KEYWORD2 ] = new SyntaxStyle( new Color( 0, 160, 96 ) );
832     aStyles[ Token.KEYWORD3 ] = new SyntaxStyle( new Color( 0, 160, 192 ) );
833     aStyles[ Token.KEYWORD4 ] = new SyntaxStyle( new Color( 192, 160, 192 ) );
834     aStyles[ Token.KEYWORD5 ] = new SyntaxStyle( new Color( 0, 192, 192 ) );
835     aStyles[ Token.FUNCTION ] = new SyntaxStyle( new Color( 128, 32, 255 ) );
836     aStyles[ Token.OPERATOR ] = new SyntaxStyle( new Color( 0, 0, 0 ) );
837     aStyles[ Token.MARKUP ] = new SyntaxStyle( new Color( 64, 64, 128 ) );
838     aStyles[ Token.LABEL ] = new SyntaxStyle( new Color( 0, 96, 96 ) );
839     aStyles[ Token.INVALID ] = new SyntaxStyle( new Color( 255, 0, 0 ) );
840 
841     return aStyles;
842   }
843 
844   /**
845   Overrides scrollRectToVisible( moArea )</code>.
846   */
847   public void scrollRectToVisible( Rectangle moArea ) {
848     new SafeScroller( moArea );
849   }
850 
851   /**
852   Set the scroll to percent from top.
853   @param adPercent Range is 0.0 to 1.0.
854   */
855   public void setScrollToPercentFromTop( double adPercent ) {
856     mdScrollToPercentFromTop = adPercent;
857   }
858 
859   /**
860   Set the scroll to percent from top.
861   @return The scroll to percent from top value.
862   */
863   public double getScrollToPercentFromTop() {
864     return mdScrollToPercentFromTop;
865   }
866 
867   /**
868   Scroll to the specified offset.
869   */
870   public void scrollToOffset( int aiOffset ) {
871     double dPercent = mdScrollToPercentFromTop;
872 
873     if( aiOffset < 0 || aiOffset > getDocument().getLength() ) return;
874 
875     Container oParent = getParent();
876     while( oParent != null && !( oParent instanceof JViewport ) ) {
877       System.out.println( "Parent: " + oParent.getClass().getName() );
878       oParent = oParent.getParent();
879     }
880     if( oParent == null ) return;
881 
882     Rectangle oViewArea = ((JViewport)oParent).getViewRect();
883     Rectangle oLineArea = null;
884     try {
885       oLineArea = modelToView( aiOffset );
886     } catch( BadLocationException aoException ) {
887       Log.write( Log.ERROR, aoException );
888       return;
889     }
890 
891     int iNetHeight = oViewArea.height - oLineArea.height;
892     int iTopHeight = (int)( iNetHeight * dPercent );
893     int iBotHeight = iNetHeight - iTopHeight;
894 
895     Rectangle oScrollArea = new Rectangle( 0, oLineArea.y - iTopHeight, oViewArea.width, oViewArea.height );
896 
897     if( oScrollArea != null ) scrollRectToVisible( oScrollArea );
898   }
899 
900   /**
901   Jump to the specified offset.
902   */
903   public void jumpToOffset( int aiOffset ) {
904     if( aiOffset < 0 || aiOffset > getDocument().getLength() ) return;
905     requestFocusInWindow();
906     scrollToOffset( aiOffset );
907     setCaretPosition( aiOffset );
908   }
909 
910   /**
911   Jump to the specified offset.
912   */
913   public void moveToOffset( int aiOffset ) {
914     if( aiOffset < 0 || aiOffset > getDocument().getLength() ) return;
915     requestFocusInWindow();
916     scrollToOffset( aiOffset );
917     moveCaretPosition( aiOffset );
918   }
919 
920   /**
921   Jump to the specified line number.
922   @param aiLineNumber The line number, not line index, to jump to.
923   @return True if the caret was jumped to the specified line number.
924   */
925   public boolean jumpToLineNumber( int aiLineNumber ) {
926     int iOffset = getStartOffsetForLineIndex( aiLineNumber - 1 );
927     if( iOffset < 0 || iOffset > getDocument().getLength() ) return false;
928     requestFocusInWindow();
929     scrollToOffset( iOffset );
930     setCaretPosition( iOffset );
931     return true;
932   }
933 
934   /**
935   Move to the specified line number.
936   @param aiLineNumber The line number, not line index, to move to.
937   @return True if the caret was moved to the specified line number.
938   */
939   public boolean moveToLineNumber( int aiLineNumber ) {
940     int iOffset = getStartOffsetForLineIndex( aiLineNumber - 1 );
941     if( iOffset < 0 || iOffset > getDocument().getLength() ) return false;
942     requestFocusInWindow();
943     scrollToOffset( iOffset );
944     moveCaretPosition( iOffset );
945     return true;
946   }
947 
948   /**
949   Find the next occurance of the search expression starting at the specified offset.
950   @param asExpression The search expression.
951   @return True if an occurance was found.
952   */
953   public boolean findNext( String asExpression ) {
954     return findNext( asExpression, Math.max( getCaret().getDot(), getCaret().getMark() ) );
955   }
956 
957   /**
958   Find the next occurance of the search expression starting at the specified offset.
959   @param asExpression The search expression.
960   @param aiOffset The start offset.
961   @return True if an occurance was found.
962   */
963   public boolean findNext( String asExpression, int aiOffset ) {
964     return findNext( asExpression, aiOffset, false );
965   }
966 
967   /**
968   Find the next occurance of the search expression starting at the specified offset.
969   @param asExpression The search expression.
970   @param aiOffset The start offset.
971   @param abWrap The wrap flag.
972   @return True if an occurance was found.
973   */
974   public boolean findNext( String asExpression, int aiOffset, boolean abWrap ) {
975     String sSearch = null;
976     try {
977       sSearch = getDocument().getText( 0, getDocument().getLength() );
978     } catch( BadLocationException aoException ) {
979       return false;
980     }
981 
982     int iStart = -1;
983     int iLength = sSearch.length();
984     StringBuffer oSearchBuffer = null;
985     StringBuffer oExpression = new StringBuffer( asExpression );
986 
987     if( abWrap ) {
988       oSearchBuffer = new StringBuffer( sSearch.substring( aiOffset ) );
989       oSearchBuffer.append( sSearch.substring( 0, iLength - aiOffset ) );
990     } else {
991       oSearchBuffer = new StringBuffer( sSearch.substring( aiOffset ) );
992     }
993 
994     // Find the occurance.
995     iStart = oSearchBuffer.indexOf( oExpression.toString() );
996     if( iStart < 0 ) return false;
997 
998     // Recalculate the start position.
999     iStart = ( aiOffset + iStart ) % iLength;
1000
1001    // Jump to the offset.
1002    setCaretPosition( iStart );
1003
1004    // Select the find.
1005    moveCaretPosition( iStart + oExpression.length() );
1006
1007    return true;
1008  }
1009
1010  /**
1011  Find the previous occurance of the search expression starting at the specified offset.
1012  @param asExpression The search expression.
1013  @param abRegex Regular expression flag.
1014  @param abWrap The wrap flag.
1015  @return True if an occurance was found.
1016  */
1017  public boolean findPrevious( String asExpression ) {
1018    return findPrevious( asExpression, Math.min( getCaret().getDot(), getCaret().getMark() ) );
1019  }
1020
1021  /**
1022  Find the previous occurance of the search expression starting at the specified offset.
1023  @param asExpression The search expression.
1024  @param aiOffset The start offset.
1025  @param abRegex Regular expression flag.
1026  @param abWrap The wrap flag.
1027  @return True if an occurance was found.
1028  */
1029  public boolean findPrevious( String asExpression, int aiOffset ) {
1030    return findPrevious( asExpression, aiOffset, false );
1031  }
1032
1033  /**
1034  Find the previous occurance of the search expression starting at the specified offset.
1035  @param asExpression The search expression.
1036  @param aiOffset The start offset.
1037  @param abWrap The wrap flag.
1038  @return True if an occurance was found.
1039  */
1040  public boolean findPrevious( String asExpression, int aiOffset, boolean abWrap ) {
1041    String sSearch = null;
1042    try {
1043      sSearch = getDocument().getText( 0, getDocument().getLength() );
1044    } catch( BadLocationException aoException ) {
1045      return false;
1046    }
1047
1048    int iStart = -1;
1049    int iLength = sSearch.length();
1050    StringBuffer oSearchBuffer = null;
1051    StringBuffer oExpression = new StringBuffer( asExpression );
1052
1053    if( abWrap ) {
1054      oSearchBuffer = new StringBuffer( sSearch.substring( aiOffset ) );
1055      oSearchBuffer.append( sSearch.substring( 0, iLength - aiOffset ) );
1056    } else {
1057      oSearchBuffer = new StringBuffer( sSearch.substring( 0, aiOffset ) );
1058    }
1059    oSearchBuffer.reverse();
1060    oExpression.reverse();
1061
1062    // Find the occurance.
1063    iStart = oSearchBuffer.indexOf( oExpression.toString() );
1064    if( iStart < 0 ) return false;
1065
1066    // Recalculate the start position.
1067    iStart = ( aiOffset - iStart - oExpression.length() ) % iLength;
1068
1069    // Jump to the offset.
1070    setCaretPosition( iStart );
1071
1072    // Select the find.
1073    moveCaretPosition( iStart + oExpression.length() );
1074
1075    return true;
1076  }
1077
1078  /**
1079  Another implementation of the <code>replaceSelection</code> method
1080  made specifically for the <code>Syntax</code>.
1081  */
1082  private void _replaceSelection( String asContent ) {
1083    if( !mbOverwrite || getSelectionStart() != getSelectionEnd() ) {
1084      replaceSelection( asContent );
1085      return;
1086    }
1087
1088    int iCaretPosition = getCaretPosition();
1089    Document oDocument = getDocument();
1090    Element oMap = oDocument.getDefaultRootElement();
1091    Element oLine = oMap.getElement( oMap.getElementIndex( iCaretPosition ) );
1092
1093    if( oLine.getEndOffset() - iCaretPosition <= asContent.length() ) {
1094      replaceSelection( asContent );
1095      return;
1096    }
1097
1098    try {
1099      oDocument.remove( iCaretPosition, asContent.length() );
1100      oDocument.insertString(  iCaretPosition, asContent, null );
1101    } catch( BadLocationException aoException ) {
1102      Log.write( Log.ERROR, aoException );
1103    }
1104  }
1105
1106  /**
1107  The default action for typing a key, modified for the <code>Syntax</code>.
1108  */
1109  private static class DefaultKeyTypedAction extends TextAction {
1110
1111    public DefaultKeyTypedAction() {
1112      super("text.editor.default.key.typed.action");
1113    }
1114
1115    public void actionPerformed( ActionEvent aoEvent ) {
1116      JTextComponent oComponent = getTextComponent( aoEvent );
1117      String sContent = aoEvent.getActionCommand();
1118      int iModifiers = aoEvent.getModifiers();
1119
1120      if( oComponent.getCursor() != moNoCursor ) oComponent.setCursor( moNoCursor );
1121
1122      if( sContent != null && sContent.length() != 0 && ( iModifiers & ActionEvent.ALT_MASK ) == 0 ) {
1123        char cChar = sContent.charAt(0);
1124        if ( ( cChar >= 0x20 ) && ( cChar != 0x7F ) ) {
1125          if( oComponent instanceof SyntaxTextPane ) {
1126            ((SyntaxTextPane)oComponent)._replaceSelection( sContent );
1127          } else {
1128            oComponent.replaceSelection( sContent );
1129          }
1130        }
1131      }
1132    }
1133  }
1134
1135  private static class InsertKeyAction extends TextAction {
1136
1137    public InsertKeyAction() {
1138      super( "insert.key" );
1139    }
1140
1141    public void actionPerformed( ActionEvent aoEvent ) {
1142      JComponent oComponent = getTextComponent( aoEvent );
1143      if( oComponent instanceof SyntaxTextPane ) {
1144        ((SyntaxTextPane)oComponent).toggleOverwrite();
1145        oComponent.repaint();
1146      }
1147    }
1148
1149  }
1150
1151  /**
1152  The delete action.
1153  */
1154  private static class DeleteAction extends TextAction {
1155
1156    public DeleteAction() {
1157      super( "delete" );
1158    }
1159
1160    public void actionPerformed( ActionEvent aoEvent ) {
1161      // Delete the current selection.
1162      JTextComponent oTextComponent = getTextComponent( aoEvent );
1163      int iDot = oTextComponent.getCaret().getDot();
1164      int iMark = oTextComponent.getCaret().getMark();
1165
1166      try {
1167        oTextComponent.getDocument().remove( iDot, iMark - iDot );
1168      } catch( BadLocationException aoException ) {
1169        Log.write( Log.ERROR, aoException );
1170      }
1171    }
1172
1173  }
1174
1175  /**
1176  The indent action.
1177  */
1178  private static class IndentAction extends TextAction {
1179
1180    public IndentAction() {
1181      super( "indent" );
1182    }
1183
1184    public void actionPerformed( ActionEvent aoEvent ) {
1185      // Indent the current selection.
1186    }
1187
1188  }
1189
1190  /**
1191  The unindent action.
1192  */
1193  private static class UnindentAction extends TextAction {
1194
1195    public UnindentAction() {
1196      super( "unindent" );
1197    }
1198
1199    public void actionPerformed( ActionEvent aoEvent ) {
1200      // Unindent the current selection.
1201    }
1202
1203  }
1204
1205  /**
1206  The undo action.
1207  */
1208  private static class UndoAction extends TextAction {
1209
1210    public UndoAction() {
1211      super( "undo" );
1212    }
1213
1214    public void actionPerformed( ActionEvent aoEvent ) {
1215      JTextComponent oTextComponent = getTextComponent( aoEvent );
1216      UndoManager oUndoManager = (UndoManager)oTextComponent.getDocument().getProperty( "text.undo.manager" );
1217      if( oUndoManager != null ) oUndoManager.undo();
1218    }
1219
1220  }
1221
1222  /**
1223  The redo action.
1224  */
1225  private static class RedoAction extends TextAction {
1226
1227    public RedoAction() {
1228      super( "redo" );
1229    }
1230
1231    public void actionPerformed( ActionEvent aoEvent ) {
1232      JTextComponent oTextComponent = getTextComponent( aoEvent );
1233      UndoManager oUndoManager = (UndoManager)oTextComponent.getDocument().getProperty( "text.undo.manager" );
1234      if( oUndoManager != null ) oUndoManager.redo();
1235    }
1236
1237  }
1238
1239  /**
1240  A mouse listener to set the cursor back to a text cursor.
1241  */
1242  private static class MouseMotionHandler extends MouseMotionAdapter {
1243
1244    public void mouseMoved( MouseEvent aoEvent ) {
1245      JTextComponent oComponent = (JTextComponent)aoEvent.getSource();
1246      if( oComponent.getCursor() != moTextCursor ) oComponent.setCursor( moTextCursor );
1247    }
1248
1249  }
1250
1251  /**
1252  The <code>Caret</code> for the <code>SyntaxTextPane</code>.
1253  */
1254  class EditorCaret extends DefaultCaret {
1255
1256    /**
1257    The shown flag.  This is used to cause the caret to blink.
1258    */
1259    private boolean mbShown;
1260
1261    /**
1262    The blinking flag.
1263    */
1264    private boolean mbBlinking;
1265
1266    /**
1267    The delay in milliseconds the caret is shown in a blink cycle.
1268    */
1269    private int miBlinkOnDelay;
1270
1271    /**
1272    The delay in milliseconds the caret is shown in a blink cycle.
1273    */
1274    private int miBlinkOffDelay;
1275
1276    /**
1277    The blink cycle timer.
1278    */
1279    private Timer moBlinkTimer;
1280
1281    /**
1282    The blink cycle off timer.
1283    */
1284    private Timer moBlinkOffTimer;
1285
1286    /**
1287    The blink cycle handler.
1288    */
1289    //private BlinkHandler moBlinkHandler;
1290
1291    /**
1292    The blink off handler.
1293    */
1294    //private BlinkOffHandler moBlinkOffHandler;
1295
1296    /**
1297    The selection highlight painter.
1298    */
1299    Highlighter.HighlightPainter moSelectionPainter;
1300
1301    /**
1302    The block caret painter.
1303    */
1304    Highlighter.HighlightPainter moBlockCaretPainter;
1305
1306    //private long mlLastTick;
1307
1308    /**
1309    Construct the caret.
1310    */
1311    public EditorCaret() {
1312      super();
1313      mbShown = true;
1314      moSelectionPainter = new SelectionHighlighter();
1315      moBlockCaretPainter = new BlockCaretHighlighter();
1316
1317      // Set up the blinking facilities.
1318      moBlinkTimer = new Timer( 1000, new BlinkHandler() );
1319      moBlinkTimer.setInitialDelay( 0 );
1320      moBlinkOffTimer = new Timer( 500, new BlinkOffHandler() );
1321      moBlinkOffTimer.setInitialDelay( 500 );
1322      moBlinkOffTimer.setRepeats( false );
1323
1324      startBlinking();
1325    }
1326
1327    /**
1328    Called when the state of the caret has changed.
1329    */
1330    public void fireStateChanged() {
1331      updateHighlighters();
1332      super.fireStateChanged();
1333    }
1334
1335    /**
1336    Get the selection highlighter.
1337    */
1338    public Highlighter.HighlightPainter getSelectionPainter() {
1339      return moSelectionPainter;
1340    }
1341
1342    /**
1343    Ensure that the caret is visible.
1344    */
1345    public void adjustVisibility( Rectangle aoArea ) {
1346      doElectricScroll( aoArea );
1347    }
1348
1349    /**
1350    Don't delete this method.  Save it for later.
1351    */
1352    public void focusGained( FocusEvent aoEvent ) {
1353      setSelectionVisible( true );
1354
1355      if( !mbBlinking ) {
1356        mbShown = true;
1357        repaint();
1358      }
1359    }
1360
1361    /**
1362    Don't delete this method.  Save it for later.
1363    */
1364    public void focusLost( FocusEvent aoEvent ) {
1365      if( !mbBlinking ) {
1366        mbShown = false;
1367        repaint();
1368      }
1369    }
1370
1371    /**
1372    Start the blink timer.
1373    */
1374    protected void startBlinking() {
1375      if( miBlinkOnDelay <= 0 || miBlinkOffDelay <= 0 ) return;
1376      moBlinkTimer.restart();
1377      mbBlinking = true;
1378    }
1379
1380    /**
1381    Stop the blink timer.
1382    */
1383    protected void stopBlinking() {
1384      mbBlinking = false;
1385      moBlinkTimer.stop();
1386      moBlinkOffTimer.stop();
1387
1388      // Show the caret.
1389      mbShown = true;
1390      repaint();
1391    }
1392
1393    /**
1394    Set the blinking flag.
1395    */
1396    public void setBlinking( boolean abBlinking ) {
1397      if( abBlinking ) {
1398        startBlinking();
1399      } else {
1400        stopBlinking();
1401      }
1402    }
1403
1404    /**
1405    Is the caret blinking?
1406    */
1407    public boolean isBlinking() {
1408      return mbBlinking;
1409    }
1410
1411    /**
1412    Override default <code>setBlinkRate()</code>.
1413    */
1414    public void setBlinkRate( int aiRate ) {
1415      setBlinkOnDelay( aiRate );
1416      setBlinkOffDelay( aiRate );
1417    }
1418
1419    /**
1420    Override the default <code>getBlinkRate()</code> to keep the
1421    text component from running the blink cycle by returning 0.
1422    */
1423    public int getBlinkRate() {
1424      return 0;
1425    }
1426
1427    /**
1428    Set the amout of time in milliseconds the caret is painted in a blink cycle.
1429    */
1430    public void setBlinkOnDelay( int aiDelay ) {
1431      stopBlinking();
1432      miBlinkOnDelay = aiDelay;
1433      moBlinkTimer.setDelay( miBlinkOnDelay + miBlinkOffDelay );
1434      moBlinkOffTimer.setInitialDelay( miBlinkOnDelay );
1435      moBlinkOffTimer.setDelay( miBlinkOnDelay );
1436      startBlinking();
1437    }
1438
1439    /**
1440    Get the blink on delay.
1441    */
1442    public int getBlinkOnDelay() {
1443      return miBlinkOnDelay;
1444    }
1445
1446    /**
1447    Set the amount of time in milliseconds the caret is not painted in a blink cycle.
1448    */
1449    public void setBlinkOffDelay( int aiDelay ) {
1450      stopBlinking();
1451      miBlinkOffDelay = aiDelay;
1452      moBlinkTimer.setDelay( miBlinkOnDelay + miBlinkOffDelay );
1453      startBlinking();
1454    }
1455
1456    /**
1457    Get the blink off delay.
1458    */
1459    public int getBlinkOffDelay() {
1460      return miBlinkOffDelay;
1461    }
1462
1463    protected void damage( Rectangle aoArea ) {
1464      if ( aoArea == null ) return;
1465
1466      Rectangle oTextAreaBounds = SyntaxTextPane.this.getBounds();
1467
1468      // Mark, this works correctly as is.  Don't break it.
1469      x = 0;
1470      y = aoArea.y;
1471      width = oTextAreaBounds.width;
1472      height = aoArea.height;
1473    }
1474
1475    public void paint( Graphics aoGraphics ) {
1476      if( !mbShown & getComponent().hasFocus() ) return;
1477
1478      Graphics2D oGraphics = (