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 = (