source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rtextarea/RTextAreaEditorKit.java@ 25584

Last change on this file since 25584 was 25584, checked in by davidb, 12 years ago

Initial cut an a text edit area for GLI that supports color syntax highlighting

File size: 67.4 KB
Line 
1/*
2 * 08/13/2004
3 *
4 * RTextAreaEditorKit.java - The editor kit used by RTextArea.
5 *
6 * This library is distributed under a modified BSD license. See the included
7 * RSyntaxTextArea.License.txt file for details.
8 */
9package org.fife.ui.rtextarea;
10
11import java.awt.*;
12import java.awt.event.*;
13import java.io.*;
14import java.text.BreakIterator;
15import java.text.DateFormat;
16import java.util.Date;
17import javax.swing.*;
18import javax.swing.text.*;
19
20import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea;
21import org.fife.ui.rsyntaxtextarea.RSyntaxUtilities;
22
23
24/**
25 * An extension of <code>DefaultEditorKit</code> that adds functionality found
26 * in <code>RTextArea</code>.
27 *
28 * @author Robert Futrell
29 * @version 0.1
30 */
31// FIXME: Replace Utilities calls with custom versions (in RSyntaxUtilities) to
32// cut down on all of the modelToViews, as each call causes
33// a getTokenList => expensive!
34public class RTextAreaEditorKit extends DefaultEditorKit {
35
36 /**
37 * The name of the action that begins recording a macro.
38 */
39 public static final String rtaBeginRecordingMacroAction = "RTA.BeginRecordingMacroAction";
40
41 /**
42 * The name of the action to decrease the font size.
43 */
44 public static final String rtaDecreaseFontSizeAction = "RTA.DecreaseFontSizeAction";
45
46 /**
47 * The name of the action that deletes the current line.
48 */
49 public static final String rtaDeleteLineAction = "RTA.DeleteLineAction";
50
51 /**
52 * The name of the action to delete the word before the caret.
53 */
54 public static final String rtaDeletePrevWordAction = "RTA.DeletePrevWordAction";
55
56 /**
57 * The name of the action taken to delete the remainder of the line (from
58 * the caret position to the end of the line).
59 */
60 public static final String rtaDeleteRestOfLineAction = "RTA.DeleteRestOfLineAction";
61
62 /**
63 * The name of the action that completes the word at the caret position
64 * with the last word in the document that starts with the text up to the
65 * caret.
66 */
67 public static final String rtaDumbCompleteWordAction = "RTA.DumbCompleteWordAction";
68
69 /**
70 * The name of the action that ends recording a macro.
71 */
72 public static final String rtaEndRecordingMacroAction = "RTA.EndRecordingMacroAction";
73
74 /**
75 * The name of the action to increase the font size.
76 */
77 public static final String rtaIncreaseFontSizeAction = "RTA.IncreaseFontSizeAction";
78
79 /**
80 * The name of the action that inverts the case of the current selection.
81 */
82 public static final String rtaInvertSelectionCaseAction = "RTA.InvertCaseAction";
83
84 /**
85 * The name of the action to join two lines.
86 */
87 public static final String rtaJoinLinesAction = "RTA.JoinLinesAction";
88
89 /**
90 * Action to move a line down.
91 */
92 public static final String rtaLineDownAction = "RTA.LineDownAction";
93
94 /**
95 * Action to move a line up.
96 */
97 public static final String rtaLineUpAction = "RTA.LineUpAction";
98
99 /**
100 * The name of the action to make the current selection lower-case.
101 */
102 public static final String rtaLowerSelectionCaseAction = "RTA.LowerCaseAction";
103
104 /**
105 * Action to select the next occurrence of the selected text.
106 */
107 public static final String rtaNextOccurrenceAction = "RTA.NextOccurrenceAction";
108
109 /**
110 * Action to select the previous occurrence of the selected text.
111 */
112 public static final String rtaPrevOccurrenceAction = "RTA.PrevOccurrenceAction";
113
114 /**
115 * Action to jump to the next bookmark.
116 */
117 public static final String rtaNextBookmarkAction = "RTA.NextBookmarkAction";
118
119 /**
120 * Action to jump to the previous bookmark.
121 */
122 public static final String rtaPrevBookmarkAction = "RTA.PrevBookmarkAction";
123
124 /**
125 * The name of the action that "plays back" the last macro.
126 */
127 public static final String rtaPlaybackLastMacroAction = "RTA.PlaybackLastMacroAction";
128
129 /**
130 * The name of the action for "redoing" the last action undone.
131 */
132 public static final String rtaRedoAction = "RTA.RedoAction";
133
134 /**
135 * The name of the action to scroll the text area down one line
136 * without changing the caret's position.
137 */
138 public static final String rtaScrollDownAction = "RTA.ScrollDownAction";
139
140 /**
141 * The name of the action to scroll the text area up one line
142 * without changing the caret's position.
143 */
144 public static final String rtaScrollUpAction = "RTA.ScrollUpAction";
145
146 /**
147 * The name of the action for "paging up" with the selection.
148 */
149 public static final String rtaSelectionPageUpAction = "RTA.SelectionPageUpAction";
150
151 /**
152 * The name of the action for "paging down" with the selection.
153 */
154 public static final String rtaSelectionPageDownAction = "RTA.SelectionPageDownAction";
155
156 /**
157 * The name of the action for "paging left" with the selection.
158 */
159 public static final String rtaSelectionPageLeftAction = "RTA.SelectionPageLeftAction";
160
161 /**
162 * The name of the action for "paging right" with the selection.
163 */
164 public static final String rtaSelectionPageRightAction = "RTA.SelectionPageRightAction";
165
166 /**
167 * The name of the action for inserting a time/date stamp.
168 */
169 public static final String rtaTimeDateAction = "RTA.TimeDateAction";
170
171 /**
172 * Toggles whether the current line has a bookmark, if this text area
173 * is in an {@link RTextScrollPane}.
174 */
175 public static final String rtaToggleBookmarkAction = "RTA.ToggleBookmarkAction";
176
177 /**
178 * The name of the action taken when the user hits the Insert key (thus
179 * toggling between insert and overwrite modes).
180 */
181 public static final String rtaToggleTextModeAction = "RTA.ToggleTextModeAction";
182
183 /**
184 * The name of the action for "undoing" the last action done.
185 */
186 public static final String rtaUndoAction = "RTA.UndoAction";
187
188 /**
189 * The name of the action for unselecting any selected text in the text
190 * area.
191 */
192 public static final String rtaUnselectAction = "RTA.UnselectAction";
193
194 /**
195 * The name of the action for making the current selection upper-case.
196 */
197 public static final String rtaUpperSelectionCaseAction = "RTA.UpperCaseAction";
198
199 /**
200 * The actions that <code>RTextAreaEditorKit</code> adds to those of
201 * the default editor kit.
202 */
203 private static final RecordableTextAction[] defaultActions = {
204 new BeginAction(beginAction, false),
205 new BeginAction(selectionBeginAction, true),
206 new BeginLineAction(beginLineAction, false),
207 new BeginLineAction(selectionBeginLineAction, true),
208 new BeginRecordingMacroAction(),
209 new BeginWordAction(beginWordAction, false),
210 new BeginWordAction(selectionBeginWordAction, true),
211 new CopyAction(),
212 new CutAction(),
213 new DefaultKeyTypedAction(),
214 new DeleteLineAction(),
215 new DeleteNextCharAction(),
216 new DeletePrevCharAction(),
217 new DeletePrevWordAction(),
218 new DeleteRestOfLineAction(),
219 new DumbCompleteWordAction(),
220 new EndAction(endAction, false),
221 new EndAction(selectionEndAction, true),
222 new EndLineAction(endLineAction, false),
223 new EndLineAction(selectionEndLineAction, true),
224 new EndRecordingMacroAction(),
225 new EndWordAction(endWordAction, false),
226 new EndWordAction(endWordAction, true),
227 new InsertBreakAction(),
228 new InsertContentAction(),
229 new InsertTabAction(),
230 new InvertSelectionCaseAction(),
231 new JoinLinesAction(),
232 new LowerSelectionCaseAction(),
233 new LineMoveAction(rtaLineUpAction, -1),
234 new LineMoveAction(rtaLineDownAction, 1),
235 new NextBookmarkAction(rtaNextBookmarkAction, true),
236 new NextBookmarkAction(rtaPrevBookmarkAction, false),
237 new NextVisualPositionAction(forwardAction, false, SwingConstants.EAST),
238 new NextVisualPositionAction(backwardAction, false, SwingConstants.WEST),
239 new NextVisualPositionAction(selectionForwardAction, true, SwingConstants.EAST),
240 new NextVisualPositionAction(selectionBackwardAction, true, SwingConstants.WEST),
241 new NextVisualPositionAction(upAction, false, SwingConstants.NORTH),
242 new NextVisualPositionAction(downAction, false, SwingConstants.SOUTH),
243 new NextVisualPositionAction(selectionUpAction, true, SwingConstants.NORTH),
244 new NextVisualPositionAction(selectionDownAction, true, SwingConstants.SOUTH),
245 new NextOccurrenceAction(rtaNextOccurrenceAction),
246 new PreviousOccurrenceAction(rtaPrevOccurrenceAction),
247 new NextWordAction(nextWordAction, false),
248 new NextWordAction(selectionNextWordAction, true),
249 new PageAction(rtaSelectionPageLeftAction, true, true),
250 new PageAction(rtaSelectionPageRightAction, false, true),
251 new PasteAction(),
252 new PlaybackLastMacroAction(),
253 new PreviousWordAction(previousWordAction, false),
254 new PreviousWordAction(selectionPreviousWordAction, true),
255 new RedoAction(),
256 new ScrollAction(rtaScrollUpAction, -1),
257 new ScrollAction(rtaScrollDownAction, 1),
258 new SelectAllAction(),
259 new SelectLineAction(),
260 new SelectWordAction(),
261 new SetReadOnlyAction(),
262 new SetWritableAction(),
263 new ToggleBookmarkAction(),
264 new ToggleTextModeAction(),
265 new UndoAction(),
266 new UnselectAction(),
267 new UpperSelectionCaseAction(),
268 new VerticalPageAction(pageUpAction, -1, false),
269 new VerticalPageAction(pageDownAction, 1, false),
270 new VerticalPageAction(rtaSelectionPageUpAction, -1, true),
271 new VerticalPageAction(rtaSelectionPageDownAction, 1, true)
272 };
273
274 /**
275 * The amount of characters read at a time when reading a file.
276 */
277 private static final int READBUFFER_SIZE = 32768;
278
279
280 /**
281 * Constructor.
282 */
283 public RTextAreaEditorKit() {
284 super();
285 }
286
287
288 /**
289 * Creates an icon row header to use in the gutter for a text area.
290 *
291 * @param textArea The text area.
292 * @return The icon row header.
293 */
294 public IconRowHeader createIconRowHeader(RTextArea textArea) {
295 return new IconRowHeader(textArea);
296 }
297
298
299 /**
300 * Creates a line number list to use in the gutter for a text area.
301 *
302 * @param textArea The text area.
303 * @return The line number list.
304 */
305 public LineNumberList createLineNumberList(RTextArea textArea) {
306 return new LineNumberList(textArea);
307 }
308
309
310 /**
311 * Fetches the set of commands that can be used
312 * on a text component that is using a model and
313 * view produced by this kit.
314 *
315 * @return the command list
316 */
317 public Action[] getActions() {
318 return defaultActions;
319 }
320
321
322 /**
323 * Inserts content from the given stream, which will be
324 * treated as plain text. This method is overridden merely
325 * so we can increase the number of characters read at a time.
326 *
327 * @param in The stream to read from
328 * @param doc The destination for the insertion.
329 * @param pos The location in the document to place the
330 * content >= 0.
331 * @exception IOException on any I/O error
332 * @exception BadLocationException if pos represents an invalid
333 * location within the document.
334 */
335 public void read(Reader in, Document doc, int pos)
336 throws IOException, BadLocationException {
337
338 char[] buff = new char[READBUFFER_SIZE];
339 int nch;
340 boolean lastWasCR = false;
341 boolean isCRLF = false;
342 boolean isCR = false;
343 int last;
344 boolean wasEmpty = (doc.getLength() == 0);
345
346 // Read in a block at a time, mapping \r\n to \n, as well as single
347 // \r's to \n's. If a \r\n is encountered, \r\n will be set as the
348 // newline string for the document, if \r is encountered it will
349 // be set as the newline character, otherwise the newline property
350 // for the document will be removed.
351 while ((nch = in.read(buff, 0, buff.length)) != -1) {
352 last = 0;
353 for (int counter = 0; counter < nch; counter++) {
354 switch (buff[counter]) {
355 case '\r':
356 if (lastWasCR) {
357 isCR = true;
358 if (counter == 0) {
359 doc.insertString(pos, "\n", null);
360 pos++;
361 }
362 else {
363 buff[counter - 1] = '\n';
364 }
365 }
366 else {
367 lastWasCR = true;
368 }
369 break;
370 case '\n':
371 if (lastWasCR) {
372 if (counter > (last + 1)) {
373 doc.insertString(pos, new String(buff, last,
374 counter - last - 1), null);
375 pos += (counter - last - 1);
376 }
377 // else nothing to do, can skip \r, next write will
378 // write \n
379 lastWasCR = false;
380 last = counter;
381 isCRLF = true;
382 }
383 break;
384 default:
385 if (lastWasCR) {
386 isCR = true;
387 if (counter == 0) {
388 doc.insertString(pos, "\n", null);
389 pos++;
390 }
391 else {
392 buff[counter - 1] = '\n';
393 }
394 lastWasCR = false;
395 }
396 break;
397 } // End of switch (buff[counter]).
398 } // End of for (int counter = 0; counter < nch; counter++).
399
400 if (last < nch) {
401 if(lastWasCR) {
402 if (last < (nch - 1)) {
403 doc.insertString(pos, new String(buff, last,
404 nch - last - 1), null);
405 pos += (nch - last - 1);
406 }
407 }
408 else {
409 doc.insertString(pos, new String(buff, last,
410 nch - last), null);
411 pos += (nch - last);
412 }
413 }
414
415 } // End of while ((nch = in.read(buff, 0, buff.length)) != -1).
416
417 if (lastWasCR) {
418 doc.insertString(pos, "\n", null);
419 isCR = true;
420 }
421
422 if (wasEmpty) {
423 if (isCRLF) {
424 doc.putProperty(EndOfLineStringProperty, "\r\n");
425 }
426 else if (isCR) {
427 doc.putProperty(EndOfLineStringProperty, "\r");
428 }
429 else {
430 doc.putProperty(EndOfLineStringProperty, "\n");
431 }
432 }
433
434 }
435
436
437 /**
438 * Creates a beep.
439 */
440 public static class BeepAction extends RecordableTextAction {
441
442 public BeepAction() {
443 super(beepAction);
444 }
445
446 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
447 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
448 }
449
450 public final String getMacroID() {
451 return beepAction;
452 }
453
454 }
455
456
457 /**
458 * Moves the caret to the beginning of the document.
459 */
460 public static class BeginAction extends RecordableTextAction {
461
462 private boolean select;
463
464 public BeginAction(String name, boolean select) {
465 super(name);
466 this.select = select;
467 }
468
469 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
470 if (select)
471 textArea.moveCaretPosition(0);
472 else
473 textArea.setCaretPosition(0);
474 }
475
476 public final String getMacroID() {
477 return getName();
478 }
479
480 }
481
482
483 /**
484 * Toggles the position of the caret between the beginning of the line,
485 * and the first non-whitespace character on the line.
486 */
487 public static class BeginLineAction extends RecordableTextAction {
488
489 private Segment currentLine = new Segment(); // For speed.
490 private boolean select;
491
492 public BeginLineAction(String name, boolean select) {
493 super(name);
494 this.select = select;
495 }
496
497 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
498
499 int newPos = 0;
500
501 try {
502
503 // Is line wrap enabled?
504 if (textArea.getLineWrap()) {
505 int offs = textArea.getCaretPosition();
506 // TODO: Replace Utilities call with custom version
507 // to cut down on all of the modelToViews, as each call
508 // causes TokenList => expensive!
509 int begOffs = Utilities.getRowStart(textArea, offs);
510 // TODO: line wrap doesn't currently toggle between
511 // the first non-whitespace char and the actual start
512 // of the line line the no-line-wrap version does.
513 newPos = begOffs;
514 }
515
516 // No line wrap - optimized for performance!
517 else {
518
519 // We use the elements instead of calling
520 // getLineOfOffset(), etc. to speed things up just a
521 // tad (i.e. micro-optimize).
522 int caretPosition = textArea.getCaretPosition();
523 Document document = textArea.getDocument();
524 Element map = document.getDefaultRootElement();
525 int currentLineNum = map.getElementIndex(caretPosition);
526 Element currentLineElement = map.getElement(currentLineNum);
527 int currentLineStart = currentLineElement.getStartOffset();
528 int currentLineEnd = currentLineElement.getEndOffset();
529 int count = currentLineEnd - currentLineStart;
530 if (count>0) { // If there are chars in the line...
531 document.getText(currentLineStart, count, currentLine);
532 int firstNonWhitespace = getFirstNonWhitespacePos();
533 firstNonWhitespace = currentLineStart +
534 (firstNonWhitespace - currentLine.offset);
535 if (caretPosition!=firstNonWhitespace) {
536 newPos = firstNonWhitespace;
537 }
538 else {
539 newPos = currentLineStart;
540 }
541 }
542 else { // Empty line (at end of the document only).
543 newPos = currentLineStart;
544 }
545
546 }
547
548 if (select) {
549 textArea.moveCaretPosition(newPos);
550 }
551 else {
552 textArea.setCaretPosition(newPos);
553 }
554 //e.consume();
555
556 } catch (BadLocationException ble) {
557 /* Shouldn't ever happen. */
558 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
559 ble.printStackTrace();
560 }
561
562 }
563
564 private final int getFirstNonWhitespacePos() {
565 int offset = currentLine.offset;
566 int end = offset + currentLine.count - 1;
567 int pos = offset;
568 char[] array = currentLine.array;
569 char currentChar = array[pos];
570 while ((currentChar=='\t' || currentChar==' ') && (++pos<end))
571 currentChar = array[pos];
572 return pos;
573 }
574
575 public final String getMacroID() {
576 return getName();
577 }
578
579 }
580
581
582 /**
583 * Action that begins recording a macro.
584 */
585 public static class BeginRecordingMacroAction extends RecordableTextAction {
586
587
588 public BeginRecordingMacroAction() {
589 super(rtaBeginRecordingMacroAction);
590 }
591
592 public BeginRecordingMacroAction(String name, Icon icon,
593 String desc, Integer mnemonic, KeyStroke accelerator) {
594 super(name, icon, desc, mnemonic, accelerator);
595 }
596
597 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
598 RTextArea.beginRecordingMacro();
599 }
600
601 public boolean isRecordable() {
602 return false; // Never record the recording of a macro!
603 }
604
605 public final String getMacroID() {
606 return rtaBeginRecordingMacroAction;
607 }
608
609 }
610
611
612 /**
613 * Positions the caret at the beginning of the word.
614 */
615 protected static class BeginWordAction extends RecordableTextAction {
616
617 private boolean select;
618
619 protected BeginWordAction(String name, boolean select) {
620 super(name);
621 this.select = select;
622 }
623
624 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
625 try {
626 int offs = textArea.getCaretPosition();
627 int begOffs = getWordStart(textArea, offs);
628 if (select)
629 textArea.moveCaretPosition(begOffs);
630 else
631 textArea.setCaretPosition(begOffs);
632 } catch (BadLocationException ble) {
633 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
634 }
635 }
636
637 public final String getMacroID() {
638 return getName();
639 }
640
641 protected int getWordStart(RTextArea textArea, int offs)
642 throws BadLocationException {
643 return Utilities.getWordStart(textArea, offs);
644 }
645
646 }
647
648
649 /**
650 * Action for copying text.
651 */
652 public static class CopyAction extends RecordableTextAction {
653
654
655 public CopyAction() {
656 super(DefaultEditorKit.copyAction);
657 }
658
659 public CopyAction(String name, Icon icon, String desc,
660 Integer mnemonic, KeyStroke accelerator) {
661 super(name, icon, desc, mnemonic, accelerator);
662 }
663
664 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
665 textArea.copy();
666 textArea.requestFocusInWindow();
667 }
668
669 public final String getMacroID() {
670 return DefaultEditorKit.copyAction;
671 }
672
673 }
674
675
676 /**
677 * Action for cutting text.
678 */
679 public static class CutAction extends RecordableTextAction {
680
681 public CutAction() {
682 super(DefaultEditorKit.cutAction);
683 }
684
685 public CutAction(String name, Icon icon, String desc,
686 Integer mnemonic, KeyStroke accelerator) {
687 super(name, icon, desc, mnemonic, accelerator);
688 }
689
690 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
691 textArea.cut();
692 textArea.requestFocusInWindow();
693 }
694
695 public final String getMacroID() {
696 return DefaultEditorKit.cutAction;
697 }
698
699 }
700
701
702 /**
703 * Action for decreasing the font size.
704 */
705 public static class DecreaseFontSizeAction extends RecordableTextAction {
706
707
708 protected float decreaseAmount;
709
710 protected static final float MINIMUM_SIZE = 2.0f;
711
712 public DecreaseFontSizeAction() {
713 super(rtaDecreaseFontSizeAction);
714 initialize();
715 }
716
717 public DecreaseFontSizeAction(String name, Icon icon, String desc,
718 Integer mnemonic, KeyStroke accelerator) {
719 super(name, icon, desc, mnemonic, accelerator);
720 initialize();
721 }
722
723 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
724 Font font = textArea.getFont();
725 float oldSize = font.getSize2D();
726 float newSize = oldSize - decreaseAmount;
727 if (newSize>=MINIMUM_SIZE) {
728 // Shrink by decreaseAmount.
729 font = font.deriveFont(newSize);
730 textArea.setFont(font);
731 }
732 else if (oldSize>MINIMUM_SIZE) {
733 // Can't shrink by full decreaseAmount, but can shrink a
734 // little bit.
735 font = font.deriveFont(MINIMUM_SIZE);
736 textArea.setFont(font);
737 }
738 else {
739 // Our font size must be at or below MINIMUM_SIZE.
740 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
741 }
742 textArea.requestFocusInWindow();
743 }
744
745 public final String getMacroID() {
746 return rtaDecreaseFontSizeAction;
747 }
748
749 protected void initialize() {
750 decreaseAmount = 1.0f;
751 }
752
753 }
754
755
756 /**
757 * The action to use when no actions in the input/action map meet the key
758 * pressed. This is actually called from the keymap I believe.
759 */
760 public static class DefaultKeyTypedAction extends RecordableTextAction {
761
762 private Action delegate;
763
764 public DefaultKeyTypedAction() {
765 super(DefaultEditorKit.defaultKeyTypedAction, null, null, null,
766 null);
767 delegate = new DefaultEditorKit.DefaultKeyTypedAction();
768 }
769
770 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
771 // DefaultKeyTypedAction *is* different across different JVM's
772 // (at least the OSX implementation must be different - Alt+Numbers
773 // inputs symbols such as '[', '{', etc., which is a *required*
774 // feature on MacBooks running with non-English input, such as
775 // German or Swedish Pro). So we can't just copy the
776 // implementation, we must delegate to it.
777 delegate.actionPerformed(e);
778 }
779
780 public final String getMacroID() {
781 return DefaultEditorKit.defaultKeyTypedAction;
782 }
783
784 }
785
786
787 /**
788 * Deletes the current line(s).
789 */
790 public static class DeleteLineAction extends RecordableTextAction {
791
792 public DeleteLineAction() {
793 super(RTextAreaEditorKit.rtaDeleteLineAction, null, null, null,
794 null);
795 }
796
797 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
798
799 if (!textArea.isEditable() || !textArea.isEnabled()) {
800 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
801 return;
802 }
803
804 int selStart = textArea.getSelectionStart();
805 int selEnd = textArea.getSelectionEnd();
806
807 try {
808
809 int line1 = textArea.getLineOfOffset(selStart);
810 int startOffs = textArea.getLineStartOffset(line1);
811 int line2 = textArea.getLineOfOffset(selEnd);
812 int endOffs = textArea.getLineEndOffset(line2);
813
814 // Don't remove the last line if no actual chars are selected
815 if (line2>line1) {
816 if (selEnd==textArea.getLineStartOffset(line2)) {
817 endOffs = selEnd;
818 }
819 }
820
821 textArea.replaceRange(null, startOffs, endOffs);
822
823 } catch (BadLocationException ble) {
824 ble.printStackTrace(); // Never happens
825 }
826
827 }
828
829 public final String getMacroID() {
830 return RTextAreaEditorKit.rtaDeleteLineAction;
831 }
832
833 }
834
835
836 /**
837 * Deletes the character of content that follows the current caret
838 * position.
839 */
840 public static class DeleteNextCharAction extends RecordableTextAction {
841
842 public DeleteNextCharAction() {
843 super(DefaultEditorKit.deleteNextCharAction, null, null,
844 null, null);
845 }
846
847 public DeleteNextCharAction(String name, Icon icon, String desc,
848 Integer mnemonic, KeyStroke accelerator) {
849 super(name, icon, desc, mnemonic, accelerator);
850 }
851
852 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
853
854 boolean beep = true;
855 if ((textArea != null) && (textArea.isEditable())) {
856 try {
857 Document doc = textArea.getDocument();
858 Caret caret = textArea.getCaret();
859 int dot = caret.getDot();
860 int mark = caret.getMark();
861 if (dot != mark) {
862 doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
863 beep = false;
864 }
865 else if (dot < doc.getLength()) {
866 int delChars = 1;
867 if (dot < doc.getLength() - 1) {
868 String dotChars = doc.getText(dot, 2);
869 char c0 = dotChars.charAt(0);
870 char c1 = dotChars.charAt(1);
871 if (c0 >= '\uD800' && c0 <= '\uDBFF' &&
872 c1 >= '\uDC00' && c1 <= '\uDFFF') {
873 delChars = 2;
874 }
875 }
876 doc.remove(dot, delChars);
877 beep = false;
878 }
879 } catch (BadLocationException bl) {
880 }
881 }
882
883 if (beep)
884 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
885
886 textArea.requestFocusInWindow();
887
888 }
889
890 public final String getMacroID() {
891 return DefaultEditorKit.deleteNextCharAction;
892 }
893
894 }
895
896
897 /**
898 * Deletes the character of content that precedes the current caret
899 * position.
900 */
901 public static class DeletePrevCharAction extends RecordableTextAction {
902
903 public DeletePrevCharAction() {
904 super(deletePrevCharAction);
905 }
906
907 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
908
909 boolean beep = true;
910 if ((textArea != null) && (textArea.isEditable())) {
911 try {
912 Document doc = textArea.getDocument();
913 Caret caret = textArea.getCaret();
914 int dot = caret.getDot();
915 int mark = caret.getMark();
916 if (dot != mark) {
917 doc.remove(Math.min(dot, mark), Math.abs(dot - mark));
918 beep = false;
919 }
920 else if (dot > 0) {
921 int delChars = 1;
922 if (dot > 1) {
923 String dotChars = doc.getText(dot - 2, 2);
924 char c0 = dotChars.charAt(0);
925 char c1 = dotChars.charAt(1);
926 if (c0 >= '\uD800' && c0 <= '\uDBFF' &&
927 c1 >= '\uDC00' && c1 <= '\uDFFF') {
928 delChars = 2;
929 }
930 }
931 doc.remove(dot - delChars, delChars);
932 beep = false;
933 }
934 } catch (BadLocationException bl) {
935 }
936 }
937
938 if (beep)
939 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
940
941 }
942
943 public final String getMacroID() {
944 return DefaultEditorKit.deletePrevCharAction;
945 }
946
947 }
948
949
950 /**
951 * Action that deletes the previous word in the text area.
952 */
953 public static class DeletePrevWordAction extends RecordableTextAction {
954
955 public DeletePrevWordAction() {
956 super(rtaDeletePrevWordAction);
957 }
958
959 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
960 if (!textArea.isEditable() || !textArea.isEnabled()) {
961 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
962 return;
963 }
964 try {
965 int end = textArea.getSelectionStart();
966 int start = getPreviousWordStart(textArea, end);
967 if (end>start) {
968 textArea.getDocument().remove(start, end-start);
969 }
970 } catch (BadLocationException ex) {
971 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
972 }
973 }
974
975 public String getMacroID() {
976 return rtaDeletePrevWordAction;
977 }
978
979 /**
980 * Returns the starting offset to delete. Exists so subclasses can
981 * override.
982 */
983 protected int getPreviousWordStart(RTextArea textArea, int end)
984 throws BadLocationException {
985 return Utilities.getPreviousWord(textArea, end);
986 }
987
988 }
989
990
991 /**
992 * Action that deletes all text from the caret position to the end of the
993 * caret's line.
994 */
995 public static class DeleteRestOfLineAction extends RecordableTextAction {
996
997
998 public DeleteRestOfLineAction() {
999 super(rtaDeleteRestOfLineAction);
1000 }
1001
1002 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1003
1004 try {
1005
1006 // We use the elements instead of calling getLineOfOffset(),
1007 // etc. to speed things up just a tad (i.e. micro-optimize).
1008 Document document = textArea.getDocument();
1009 int caretPosition = textArea.getCaretPosition();
1010 Element map = document.getDefaultRootElement();
1011 int currentLineNum = map.getElementIndex(caretPosition);
1012 Element currentLineElement = map.getElement(currentLineNum);
1013 // Always take -1 as we don't want to remove the newline.
1014 int currentLineEnd = currentLineElement.getEndOffset()-1;
1015 if (caretPosition<currentLineEnd) {
1016 document.remove(caretPosition,
1017 currentLineEnd-caretPosition);
1018 }
1019
1020 } catch (BadLocationException ble) {
1021 ble.printStackTrace();
1022 }
1023
1024 }
1025
1026 public final String getMacroID() {
1027 return rtaDeleteRestOfLineAction;
1028 }
1029
1030 }
1031
1032
1033 /**
1034 * Finds the most recent word in the document that matches the "word" up
1035 * to the current caret position, and auto-completes the rest. Repeatedly
1036 * calling this action at the same location in the document goes one
1037 * match back each time it is called.
1038 */
1039 public static class DumbCompleteWordAction extends RecordableTextAction {
1040
1041 private int lastWordStart;
1042 private int lastDot;
1043 private int searchOffs;
1044 private String lastPrefix;
1045
1046 public DumbCompleteWordAction() {
1047 super(rtaDumbCompleteWordAction);
1048 lastWordStart = searchOffs = lastDot = -1;
1049 }
1050
1051 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1052
1053 if (!textArea.isEditable() || !textArea.isEnabled()) {
1054 return;
1055 }
1056
1057 try {
1058
1059 int dot = textArea.getCaretPosition();
1060 if (dot == 0) {
1061 return;
1062 }
1063
1064 int curWordStart = Utilities.getWordStart(textArea, dot - 1);
1065
1066 if (lastWordStart!=curWordStart || dot!=lastDot) {
1067 lastPrefix = textArea.getText(curWordStart,dot-curWordStart);
1068 // Utilities.getWordStart() treats spans of whitespace and
1069 // single non-letter chars as "words."
1070 if (lastPrefix.length()==0 ||
1071 !Character.isLetter(lastPrefix.charAt(lastPrefix.length()-1))) {
1072 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1073 return;
1074 }
1075 lastWordStart = dot - lastPrefix.length();
1076 searchOffs = lastWordStart;
1077 }
1078
1079 while (searchOffs > 0) {
1080 int wordStart = Utilities.getPreviousWord(textArea,
1081 searchOffs);
1082 if (wordStart==BreakIterator.DONE) {
1083 UIManager.getLookAndFeel().provideErrorFeedback(
1084 textArea);
1085 break;
1086 }
1087 int end = Utilities.getWordEnd(textArea, wordStart);
1088 String word = textArea.getText(wordStart, end - wordStart);
1089 searchOffs = wordStart;
1090 if (word.startsWith(lastPrefix)) {
1091 textArea.replaceRange(word, lastWordStart, dot);
1092 lastDot = textArea.getCaretPosition(); // Maybe shifted
1093 break;
1094 }
1095 }
1096
1097 } catch (BadLocationException ble) { // Never happens
1098 ble.printStackTrace();
1099 }
1100
1101 }
1102
1103 public final String getMacroID() {
1104 return getName();
1105 }
1106
1107 }
1108
1109
1110 /**
1111 * Moves the caret to the end of the document.
1112 */
1113 public static class EndAction extends RecordableTextAction {
1114
1115 private boolean select;
1116
1117 public EndAction(String name, boolean select) {
1118 super(name);
1119 this.select = select;
1120 }
1121
1122 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1123 int dot = getVisibleEnd(textArea);
1124 if (select)
1125 textArea.moveCaretPosition(dot);
1126 else
1127 textArea.setCaretPosition(dot);
1128 }
1129
1130 public final String getMacroID() {
1131 return getName();
1132 }
1133
1134 protected int getVisibleEnd(RTextArea textArea) {
1135 return textArea.getDocument().getLength();
1136 }
1137
1138 }
1139
1140
1141 /**
1142 * Positions the caret at the end of the line.
1143 */
1144 public static class EndLineAction extends RecordableTextAction {
1145
1146 private boolean select;
1147
1148 public EndLineAction(String name, boolean select) {
1149 super(name);
1150 this.select = select;
1151 }
1152
1153 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1154 int offs = textArea.getCaretPosition();
1155 int endOffs = 0;
1156 try {
1157 if (textArea.getLineWrap()) {
1158 // Must check per character, since one logical line may be
1159 // many physical lines.
1160 // FIXME: Replace Utilities call with custom version to
1161 // cut down on all of the modelToViews, as each call causes
1162 // a getTokenList => expensive!
1163 endOffs = Utilities.getRowEnd(textArea, offs);
1164 }
1165 else {
1166 Element root = textArea.getDocument().getDefaultRootElement();
1167 int line = root.getElementIndex(offs);
1168 endOffs = root.getElement(line).getEndOffset() - 1;
1169 }
1170 if (select) {
1171 textArea.moveCaretPosition(endOffs);
1172 }
1173 else {
1174 textArea.setCaretPosition(endOffs);
1175 }
1176 } catch (Exception ex) {
1177 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1178 }
1179 }
1180
1181 public final String getMacroID() {
1182 return getName();
1183 }
1184
1185 }
1186
1187
1188 /**
1189 * Action that ends recording a macro.
1190 */
1191 public static class EndRecordingMacroAction extends RecordableTextAction {
1192
1193
1194 public EndRecordingMacroAction() {
1195 super(rtaEndRecordingMacroAction);
1196 }
1197
1198 public EndRecordingMacroAction(String name, Icon icon,
1199 String desc, Integer mnemonic, KeyStroke accelerator) {
1200 super(name, icon, desc, mnemonic, accelerator);
1201 }
1202
1203 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1204 RTextArea.endRecordingMacro();
1205 }
1206
1207 public final String getMacroID() {
1208 return rtaEndRecordingMacroAction;
1209 }
1210
1211 public boolean isRecordable() {
1212 return false; // Never record the recording of a macro!
1213 }
1214
1215 }
1216
1217
1218 /**
1219 * Positions the caret at the end of the word.
1220 */
1221 protected static class EndWordAction extends RecordableTextAction {
1222
1223 private boolean select;
1224
1225 protected EndWordAction(String name, boolean select) {
1226 super(name);
1227 this.select = select;
1228 }
1229
1230 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1231 try {
1232 int offs = textArea.getCaretPosition();
1233 int endOffs = getWordEnd(textArea, offs);
1234 if (select)
1235 textArea.moveCaretPosition(endOffs);
1236 else
1237 textArea.setCaretPosition(endOffs);
1238 } catch (BadLocationException ble) {
1239 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1240 }
1241 }
1242
1243 public final String getMacroID() {
1244 return getName();
1245 }
1246
1247 protected int getWordEnd(RTextArea textArea, int offs)
1248 throws BadLocationException {
1249 return Utilities.getWordEnd(textArea, offs);
1250 }
1251
1252 }
1253
1254
1255 /**
1256 * Action for increasing the font size.
1257 */
1258 public static class IncreaseFontSizeAction extends RecordableTextAction {
1259
1260
1261 protected float increaseAmount;
1262
1263 protected static final float MAXIMUM_SIZE = 40.0f;
1264
1265 public IncreaseFontSizeAction() {
1266 super(rtaIncreaseFontSizeAction);
1267 initialize();
1268 }
1269
1270 public IncreaseFontSizeAction(String name, Icon icon, String desc,
1271 Integer mnemonic, KeyStroke accelerator) {
1272 super(name, icon, desc, mnemonic, accelerator);
1273 initialize();
1274 }
1275
1276 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1277 Font font = textArea.getFont();
1278 float oldSize = font.getSize2D();
1279 float newSize = oldSize + increaseAmount;
1280 if (newSize<=MAXIMUM_SIZE) {
1281 // Grow by increaseAmount.
1282 font = font.deriveFont(newSize);
1283 textArea.setFont(font);
1284 }
1285 else if (oldSize<MAXIMUM_SIZE) {
1286 // Can't grow by full increaseAmount, but can grow a
1287 // little bit.
1288 font = font.deriveFont(MAXIMUM_SIZE);
1289 textArea.setFont(font);
1290 }
1291 else {
1292 // Our font size must be at or bigger than MAXIMUM_SIZE.
1293 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1294 }
1295 textArea.requestFocusInWindow();
1296 }
1297
1298 public final String getMacroID() {
1299 return rtaIncreaseFontSizeAction;
1300 }
1301
1302 protected void initialize() {
1303 increaseAmount = 1.0f;
1304 }
1305
1306 }
1307
1308
1309 /**
1310 * Action for when the user presses the Enter key.
1311 */
1312 public static class InsertBreakAction extends RecordableTextAction {
1313
1314
1315 public InsertBreakAction() {
1316 super(DefaultEditorKit.insertBreakAction);
1317 }
1318
1319 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1320 if (!textArea.isEditable() || !textArea.isEnabled()) {
1321 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1322 return;
1323 }
1324 textArea.replaceSelection("\n");
1325 }
1326
1327 public final String getMacroID() {
1328 return DefaultEditorKit.insertBreakAction;
1329 }
1330
1331 /*
1332 * Overridden for Sun bug 4515750. Sun fixed this in a more complicated
1333 * way, but I'm not sure why. See BasicTextUI#getActionMap() and
1334 * BasicTextUI.TextActionWrapper.
1335 */
1336 public boolean isEnabled() {
1337 JTextComponent tc = getTextComponent(null);
1338 return (tc==null || tc.isEditable()) ? super.isEnabled() : false;
1339 }
1340
1341 }
1342
1343
1344 /**
1345 * Action taken when content is to be inserted.
1346 */
1347 public static class InsertContentAction extends RecordableTextAction {
1348
1349
1350 public InsertContentAction() {
1351 super(DefaultEditorKit.insertContentAction, null, null, null,
1352 null);
1353 }
1354
1355 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1356 if (!textArea.isEditable() || !textArea.isEnabled()) {
1357 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1358 return;
1359 }
1360 String content = e.getActionCommand();
1361 if (content != null)
1362 textArea.replaceSelection(content);
1363 else
1364 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1365 }
1366
1367 public final String getMacroID() {
1368 return DefaultEditorKit.insertContentAction;
1369 }
1370
1371 }
1372
1373
1374 /**
1375 * Places a tab character into the document. If there is a selection, it
1376 * is removed before the tab is added.
1377 */
1378 public static class InsertTabAction extends RecordableTextAction {
1379
1380
1381 public InsertTabAction() {
1382 super(insertTabAction);
1383 }
1384
1385 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1386 if (!textArea.isEditable() || !textArea.isEnabled()) {
1387 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1388 return;
1389 }
1390 textArea.replaceSelection("\t");
1391 }
1392
1393 public final String getMacroID() {
1394 return DefaultEditorKit.insertTabAction;
1395 }
1396
1397 }
1398
1399
1400 /**
1401 * Action to invert the selection's case.
1402 */
1403 public static class InvertSelectionCaseAction extends RecordableTextAction {
1404
1405
1406 public InvertSelectionCaseAction() {
1407 super(rtaInvertSelectionCaseAction);
1408 }
1409
1410 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1411 if (!textArea.isEditable() || !textArea.isEnabled()) {
1412 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1413 return;
1414 }
1415 String selection = textArea.getSelectedText();
1416 if (selection!=null) {
1417 StringBuffer buffer = new StringBuffer(selection);
1418 int length = buffer.length();
1419 for (int i=0; i<length; i++) {
1420 char c = buffer.charAt(i);
1421 if (Character.isUpperCase(c))
1422 buffer.setCharAt(i, Character.toLowerCase(c));
1423 else if (Character.isLowerCase(c))
1424 buffer.setCharAt(i, Character.toUpperCase(c));
1425 }
1426 textArea.replaceSelection(buffer.toString());
1427 }
1428 textArea.requestFocusInWindow();
1429 }
1430
1431 public final String getMacroID() {
1432 return getName();
1433 }
1434
1435 }
1436
1437
1438 /**
1439 * Action to join the current line and the following line.
1440 */
1441 public static class JoinLinesAction extends RecordableTextAction {
1442
1443 public JoinLinesAction() {
1444 super(rtaJoinLinesAction);
1445 }
1446
1447 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1448 if (!textArea.isEditable() || !textArea.isEnabled()) {
1449 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1450 return;
1451 }
1452 try {
1453 Caret c = textArea.getCaret();
1454 int caretPos = c.getDot();
1455 Document doc = textArea.getDocument();
1456 Element map = doc.getDefaultRootElement();
1457 int lineCount = map.getElementCount();
1458 int line = map.getElementIndex(caretPos);
1459 if (line==lineCount-1) {
1460 UIManager.getLookAndFeel().
1461 provideErrorFeedback(textArea);
1462 return;
1463 }
1464 Element lineElem = map.getElement(line);
1465 caretPos = lineElem.getEndOffset() - 1;
1466 c.setDot(caretPos); // Gets rid of any selection.
1467 doc.remove(caretPos, 1); // Should be '\n'.
1468 } catch (BadLocationException ble) {
1469 /* Shouldn't ever happen. */
1470 ble.printStackTrace();
1471 }
1472 textArea.requestFocusInWindow();
1473 }
1474
1475 public final String getMacroID() {
1476 return getName();
1477 }
1478
1479 }
1480
1481
1482 /**
1483 * Action that moves a line up or down.
1484 */
1485 public static class LineMoveAction extends RecordableTextAction {
1486
1487 private int moveAmt;
1488
1489 public LineMoveAction(String name, int moveAmt) {
1490 super(name);
1491 this.moveAmt = moveAmt;
1492 }
1493
1494 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1495 if (!textArea.isEditable() || !textArea.isEnabled()) {
1496 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1497 return;
1498 }
1499 try {
1500 int caret = textArea.getCaretPosition();
1501 Document doc = textArea.getDocument();
1502 Element root = doc.getDefaultRootElement();
1503 int line = root.getElementIndex(caret);
1504 if (moveAmt==-1 && line>0) {
1505 moveLineUp(textArea, line);
1506 }
1507 else if (moveAmt==1 && line<root.getElementCount()-1) {
1508 moveLineDown(textArea, line);
1509 }
1510 else {
1511 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1512 return;
1513 }
1514 } catch (BadLocationException ble) {
1515 // Never happens.
1516 ble.printStackTrace();
1517 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1518 return;
1519 }
1520 }
1521
1522 public final String getMacroID() {
1523 return getName();
1524 }
1525
1526 private final void moveLineDown(RTextArea textArea, int line)
1527 throws BadLocationException {
1528 Document doc = textArea.getDocument();
1529 Element root = doc.getDefaultRootElement();
1530 Element elem = root.getElement(line);
1531 int start = elem.getStartOffset();
1532 int end = elem.getEndOffset();
1533 int caret = textArea.getCaretPosition();
1534 int caretOffset = caret - start;
1535 String text = doc.getText(start, end-start);
1536 doc.remove(start, end-start);
1537 Element elem2 = root.getElement(line); // not "line+1" - removed.
1538 //int start2 = elem2.getStartOffset();
1539 int end2 = elem2.getEndOffset();
1540 doc.insertString(end2, text, null);
1541 elem = root.getElement(line+1);
1542 textArea.setCaretPosition(elem.getStartOffset()+caretOffset);
1543 }
1544
1545 private final void moveLineUp(RTextArea textArea, int line)
1546 throws BadLocationException {
1547 Document doc = textArea.getDocument();
1548 Element root = doc.getDefaultRootElement();
1549 int lineCount = root.getElementCount();
1550 Element elem = root.getElement(line);
1551 int start = elem.getStartOffset();
1552 int end = line==lineCount-1 ? elem.getEndOffset()-1 :
1553 elem.getEndOffset();
1554 int caret = textArea.getCaretPosition();
1555 int caretOffset = caret - start;
1556 String text = doc.getText(start, end-start);
1557 if (line==lineCount-1) {
1558 start--; // Remove previous line's ending \n
1559 }
1560 doc.remove(start, end-start);
1561 Element elem2 = root.getElement(line-1);
1562 int start2 = elem2.getStartOffset();
1563 //int end2 = elem2.getEndOffset();
1564 if (line==lineCount-1) {
1565 text += '\n';
1566 }
1567 doc.insertString(start2, text, null);
1568 //caretOffset = Math.min(start2+caretOffset, end2-1);
1569 textArea.setCaretPosition(start2+caretOffset);
1570 }
1571
1572 }
1573
1574
1575 /**
1576 * Action to make the selection lower-case.
1577 */
1578 public static class LowerSelectionCaseAction extends RecordableTextAction {
1579
1580 public LowerSelectionCaseAction() {
1581 super(rtaLowerSelectionCaseAction);
1582 }
1583
1584 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1585 if (!textArea.isEditable() || !textArea.isEnabled()) {
1586 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1587 return;
1588 }
1589 String selection = textArea.getSelectedText();
1590 if (selection!=null)
1591 textArea.replaceSelection(selection.toLowerCase());
1592 textArea.requestFocusInWindow();
1593 }
1594
1595 public final String getMacroID() {
1596 return getName();
1597 }
1598
1599 }
1600
1601
1602 /**
1603 * Action that moves the caret to the next (or previous) bookmark.
1604 */
1605 public static class NextBookmarkAction extends RecordableTextAction {
1606
1607 private boolean forward;
1608
1609 public NextBookmarkAction(String name, boolean forward) {
1610 super(name);
1611 this.forward = forward;
1612 }
1613
1614 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1615
1616 Gutter gutter = RSyntaxUtilities.getGutter(textArea);
1617 if (gutter!=null) {
1618
1619 try {
1620
1621 GutterIconInfo[] bookmarks = gutter.getBookmarks();
1622 if (bookmarks.length==0) {
1623 UIManager.getLookAndFeel().
1624 provideErrorFeedback(textArea);
1625 return;
1626 }
1627
1628 GutterIconInfo moveTo = null;
1629 int curLine = textArea.getCaretLineNumber();
1630
1631 if (forward) {
1632 for (int i=0; i<bookmarks.length; i++) {
1633 GutterIconInfo bookmark = bookmarks[i];
1634 int offs = bookmark.getMarkedOffset();
1635 int line = textArea.getLineOfOffset(offs);
1636 if (line>curLine) {
1637 moveTo = bookmark;
1638 break;
1639 }
1640 }
1641 if (moveTo==null) { // Loop back to beginning
1642 moveTo = bookmarks[0];
1643 }
1644 }
1645 else {
1646 for (int i=bookmarks.length-1; i>=0; i--) {
1647 GutterIconInfo bookmark = bookmarks[i];
1648 int offs = bookmark.getMarkedOffset();
1649 int line = textArea.getLineOfOffset(offs);
1650 if (line<curLine) {
1651 moveTo = bookmark;
1652 break;
1653 }
1654 }
1655 if (moveTo==null) { // Loop back to end
1656 moveTo = bookmarks[bookmarks.length-1];
1657 }
1658 }
1659
1660 int offs = moveTo.getMarkedOffset();
1661 if (textArea instanceof RSyntaxTextArea) {
1662 RSyntaxTextArea rsta = (RSyntaxTextArea)textArea;
1663 if (rsta.isCodeFoldingEnabled()) {
1664 rsta.getFoldManager().
1665 ensureOffsetNotInClosedFold(offs);
1666 }
1667 }
1668 int line = textArea.getLineOfOffset(offs);
1669 offs = textArea.getLineStartOffset(line);
1670 textArea.setCaretPosition(offs);
1671
1672 } catch (BadLocationException ble) { // Never happens
1673 UIManager.getLookAndFeel().
1674 provideErrorFeedback(textArea);
1675 ble.printStackTrace();
1676 }
1677 }
1678
1679 }
1680
1681 public final String getMacroID() {
1682 return getName();
1683 }
1684
1685 }
1686
1687
1688 /**
1689 * Selects the next occurrence of the text last selected.
1690 */
1691 public static class NextOccurrenceAction extends RecordableTextAction {
1692
1693 public NextOccurrenceAction(String name) {
1694 super(name);
1695 }
1696
1697 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1698 String selectedText = textArea.getSelectedText();
1699 if (selectedText == null || selectedText.length() == 0) {
1700 selectedText = RTextArea.getSelectedOccurrenceText();
1701 if (selectedText == null || selectedText.length() == 0) {
1702 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1703 return;
1704 }
1705 }
1706 SearchContext context = new SearchContext(selectedText);
1707 if (!SearchEngine.find(textArea, context)) {
1708 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1709 }
1710 RTextArea.setSelectedOccurrenceText(selectedText);
1711 }
1712
1713 public final String getMacroID() {
1714 return getName();
1715 }
1716
1717 }
1718
1719
1720 /**
1721 * Action to move the selection and/or caret. Constructor indicates
1722 * direction to use.
1723 */
1724 public static class NextVisualPositionAction extends RecordableTextAction {
1725
1726 private boolean select;
1727 private int direction;
1728
1729 public NextVisualPositionAction(String nm, boolean select, int dir) {
1730 super(nm);
1731 this.select = select;
1732 this.direction = dir;
1733 }
1734
1735 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1736
1737 Caret caret = textArea.getCaret();
1738 int dot = caret.getDot();
1739
1740 /*
1741 * Move to the beginning/end of selection on a "non-shifted"
1742 * left- or right-keypress. We shouldn't have to worry about
1743 * navigation filters as, if one is being used, it let us get
1744 * to that position before.
1745 */
1746 if (!select) {
1747 switch (direction) {
1748 case SwingConstants.EAST:
1749 int mark = caret.getMark();
1750 if (dot!=mark) {
1751 caret.setDot(Math.max(dot, mark));
1752 return;
1753 }
1754 break;
1755 case SwingConstants.WEST:
1756 mark = caret.getMark();
1757 if (dot!=mark) {
1758 caret.setDot(Math.min(dot, mark));
1759 return;
1760 }
1761 break;
1762 default:
1763 }
1764 }
1765
1766 Position.Bias[] bias = new Position.Bias[1];
1767 Point magicPosition = caret.getMagicCaretPosition();
1768
1769 try {
1770
1771 if(magicPosition == null &&
1772 (direction == SwingConstants.NORTH ||
1773 direction == SwingConstants.SOUTH)) {
1774 Rectangle r = textArea.modelToView(dot);
1775 magicPosition = new Point(r.x, r.y);
1776 }
1777
1778 NavigationFilter filter = textArea.getNavigationFilter();
1779
1780 if (filter != null) {
1781 dot = filter.getNextVisualPositionFrom(textArea, dot,
1782 Position.Bias.Forward, direction, bias);
1783 }
1784 else {
1785 dot = textArea.getUI().getNextVisualPositionFrom(
1786 textArea, dot,
1787 Position.Bias.Forward, direction, bias);
1788 }
1789 if (select)
1790 caret.moveDot(dot);
1791 else
1792 caret.setDot(dot);
1793
1794 if(magicPosition != null &&
1795 (direction == SwingConstants.NORTH ||
1796 direction == SwingConstants.SOUTH)) {
1797 caret.setMagicCaretPosition(magicPosition);
1798 }
1799
1800 } catch (BadLocationException ble) {
1801 ble.printStackTrace();
1802 }
1803
1804 }
1805
1806 public final String getMacroID() {
1807 return getName();
1808 }
1809
1810 }
1811
1812
1813 /**
1814 * Positions the caret at the next word.
1815 */
1816 public static class NextWordAction extends RecordableTextAction {
1817
1818 private boolean select;
1819
1820 public NextWordAction(String name, boolean select) {
1821 super(name);
1822 this.select = select;
1823 }
1824
1825 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1826
1827 int offs = textArea.getCaretPosition();
1828 int oldOffs = offs;
1829 Element curPara = Utilities.getParagraphElement(textArea, offs);
1830
1831 try {
1832 offs = getNextWord(textArea, offs);
1833 if(offs >= curPara.getEndOffset() &&
1834 oldOffs != curPara.getEndOffset() - 1) {
1835 // we should first move to the end of current paragraph
1836 // http://bugs.sun.com/view_bug.do?bug_id=4278839
1837 offs = curPara.getEndOffset() - 1;
1838 }
1839 } catch (BadLocationException ble) {
1840 int end = textArea.getDocument().getLength();
1841 if (offs != end) {
1842 if(oldOffs != curPara.getEndOffset() - 1)
1843 offs = curPara.getEndOffset() - 1;
1844 else
1845 offs = end;
1846 }
1847 }
1848
1849 if (select)
1850 textArea.moveCaretPosition(offs);
1851 else
1852 textArea.setCaretPosition(offs);
1853
1854 }
1855
1856 public final String getMacroID() {
1857 return getName();
1858 }
1859
1860 protected int getNextWord(RTextArea textArea, int offs)
1861 throws BadLocationException {
1862 return Utilities.getNextWord(textArea, offs);
1863 }
1864
1865 }
1866
1867
1868 /**
1869 * Pages one view to the left or right.
1870 */
1871 static class PageAction extends RecordableTextAction {
1872
1873
1874 private boolean select;
1875 private boolean left;
1876
1877 public PageAction(String name, boolean left, boolean select) {
1878 super(name);
1879 this.select = select;
1880 this.left = left;
1881 }
1882
1883 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1884
1885 int selectedIndex;
1886 Rectangle visible = new Rectangle();
1887 textArea.computeVisibleRect(visible);
1888 if (left)
1889 visible.x = Math.max(0, visible.x - visible.width);
1890 else
1891 visible.x += visible.width;
1892
1893 selectedIndex = textArea.getCaretPosition();
1894 if(selectedIndex != -1) {
1895 if (left) {
1896 selectedIndex = textArea.viewToModel(
1897 new Point(visible.x, visible.y));
1898 }
1899 else {
1900 selectedIndex = textArea.viewToModel(
1901 new Point(visible.x + visible.width - 1,
1902 visible.y + visible.height - 1));
1903 }
1904 Document doc = textArea.getDocument();
1905 if ((selectedIndex != 0) &&
1906 (selectedIndex > (doc.getLength()-1))) {
1907 selectedIndex = doc.getLength()-1;
1908 }
1909 else if(selectedIndex < 0) {
1910 selectedIndex = 0;
1911 }
1912 if (select)
1913 textArea.moveCaretPosition(selectedIndex);
1914 else
1915 textArea.setCaretPosition(selectedIndex);
1916 }
1917
1918 }
1919
1920 public final String getMacroID() {
1921 return getName();
1922 }
1923
1924 }
1925
1926
1927 /**
1928 * Action for pasting text.
1929 */
1930 public static class PasteAction extends RecordableTextAction {
1931
1932
1933 public PasteAction() {
1934 super(DefaultEditorKit.pasteAction);
1935 }
1936
1937 public PasteAction(String name, Icon icon, String desc,
1938 Integer mnemonic, KeyStroke accelerator) {
1939 super(name, icon, desc, mnemonic, accelerator);
1940 }
1941
1942 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1943 textArea.paste();
1944 textArea.requestFocusInWindow();
1945 }
1946
1947 public final String getMacroID() {
1948 return DefaultEditorKit.pasteAction;
1949 }
1950
1951 }
1952
1953
1954 /**
1955 * "Plays back" the last macro recorded.
1956 */
1957 public static class PlaybackLastMacroAction extends RecordableTextAction {
1958
1959
1960 public PlaybackLastMacroAction() {
1961 super(rtaPlaybackLastMacroAction);
1962 }
1963
1964 public PlaybackLastMacroAction(String name, Icon icon,
1965 String desc, Integer mnemonic, KeyStroke accelerator) {
1966 super(name, icon, desc, mnemonic, accelerator);
1967 }
1968
1969 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1970 textArea.playbackLastMacro();
1971 }
1972
1973 public boolean isRecordable() {
1974 return false; // Don't record macro playbacks.
1975 }
1976
1977 public final String getMacroID() {
1978 return rtaPlaybackLastMacroAction;
1979 }
1980
1981 }
1982
1983
1984 /**
1985 * Select the previous occurrence of the text last selected.
1986 */
1987 public static class PreviousOccurrenceAction extends RecordableTextAction {
1988
1989 public PreviousOccurrenceAction(String name) {
1990 super(name);
1991 }
1992
1993 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
1994 String selectedText = textArea.getSelectedText();
1995 if (selectedText == null || selectedText.length() == 0) {
1996 selectedText = RTextArea.getSelectedOccurrenceText();
1997 if (selectedText == null || selectedText.length() == 0) {
1998 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
1999 return;
2000 }
2001 }
2002 SearchContext context = new SearchContext(selectedText);
2003 context.setSearchForward(false);
2004 if (!SearchEngine.find(textArea, context)) {
2005 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
2006 }
2007 RTextArea.setSelectedOccurrenceText(selectedText);
2008 }
2009
2010 public final String getMacroID() {
2011 return getName();
2012 }
2013
2014 }
2015
2016
2017 /**
2018 * Positions the caret at the beginning of the previous word.
2019 */
2020 public static class PreviousWordAction extends RecordableTextAction {
2021
2022 private boolean select;
2023
2024 public PreviousWordAction(String name, boolean select) {
2025 super(name);
2026 this.select = select;
2027 }
2028
2029 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2030
2031 int offs = textArea.getCaretPosition();
2032 boolean failed = false;
2033 try {
2034
2035 Element curPara = Utilities.getParagraphElement(textArea, offs);
2036 offs = getPreviousWord(textArea, offs);
2037 if(offs < curPara.getStartOffset()) {
2038 offs = Utilities.getParagraphElement(textArea, offs).
2039 getEndOffset() - 1;
2040 }
2041
2042 } catch (BadLocationException bl) {
2043 if (offs != 0)
2044 offs = 0;
2045 else
2046 failed = true;
2047 }
2048
2049 if (!failed) {
2050 if (select)
2051 textArea.moveCaretPosition(offs);
2052 else
2053 textArea.setCaretPosition(offs);
2054 }
2055 else
2056 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
2057
2058 }
2059
2060 public final String getMacroID() {
2061 return getName();
2062 }
2063
2064 protected int getPreviousWord(RTextArea textArea, int offs)
2065 throws BadLocationException {
2066 return Utilities.getPreviousWord(textArea, offs);
2067 }
2068
2069 }
2070
2071
2072 /**
2073 * Re-does the last action undone.
2074 */
2075 public static class RedoAction extends RecordableTextAction {
2076
2077
2078 public RedoAction() {
2079 super(rtaRedoAction);
2080 }
2081
2082 public RedoAction(String name, Icon icon, String desc,
2083 Integer mnemonic, KeyStroke accelerator) {
2084 super(name, icon, desc, mnemonic, accelerator);
2085 }
2086
2087 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2088 if (textArea.isEnabled() && textArea.isEditable()) {
2089 textArea.redoLastAction();
2090 textArea.requestFocusInWindow();
2091 }
2092 }
2093
2094 public final String getMacroID() {
2095 return rtaRedoAction;
2096 }
2097
2098 }
2099
2100
2101 /**
2102 * Scrolls the text area one line up or down, without changing
2103 * the caret position.
2104 */
2105 public static class ScrollAction extends RecordableTextAction {
2106
2107 private int delta;
2108
2109 public ScrollAction(String name, int delta) {
2110 super(name);
2111 this.delta = delta;
2112 }
2113
2114 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2115 Container parent = textArea.getParent();
2116 if (parent instanceof JViewport) {
2117 JViewport viewport = (JViewport)parent;
2118 Point p = viewport.getViewPosition();
2119 p.y += delta*textArea.getLineHeight();
2120 if (p.y<0) {
2121 p.y = 0;
2122 }
2123 else {
2124 Rectangle viewRect = viewport.getViewRect();
2125 int visibleEnd = p.y + viewRect.height;
2126 if (visibleEnd>=textArea.getHeight()) {
2127 p.y = textArea.getHeight() - viewRect.height;
2128 }
2129 }
2130 viewport.setViewPosition(p);
2131 }
2132 }
2133
2134 public final String getMacroID() {
2135 return getName();
2136 }
2137
2138 }
2139
2140
2141 /**
2142 * Selects the entire document.
2143 */
2144 public static class SelectAllAction extends RecordableTextAction {
2145
2146
2147 public SelectAllAction() {
2148 super(selectAllAction);
2149 }
2150
2151 public SelectAllAction(String name, Icon icon, String desc,
2152 Integer mnemonic, KeyStroke accelerator) {
2153 super(name, icon, desc, mnemonic, accelerator);
2154 }
2155
2156 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2157 Document doc = textArea.getDocument();
2158 textArea.setCaretPosition(0);
2159 textArea.moveCaretPosition(doc.getLength());
2160 }
2161
2162 public final String getMacroID() {
2163 return DefaultEditorKit.selectAllAction;
2164 }
2165
2166 }
2167
2168
2169 /**
2170 * Selects the line around the caret.
2171 */
2172 public static class SelectLineAction extends RecordableTextAction {
2173
2174 private Action start;
2175 private Action end;
2176
2177 public SelectLineAction() {
2178 super(selectLineAction);
2179 start = new BeginLineAction("pigdog", false);
2180 end = new EndLineAction("pigdog", true);
2181 }
2182
2183 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2184 start.actionPerformed(e);
2185 end.actionPerformed(e);
2186 }
2187
2188 public final String getMacroID() {
2189 return DefaultEditorKit.selectLineAction;
2190 }
2191
2192 }
2193
2194
2195 /**
2196 * Selects the word around the caret.
2197 */
2198 public static class SelectWordAction extends RecordableTextAction {
2199
2200 protected Action start;
2201 protected Action end;
2202
2203 public SelectWordAction() {
2204 super(selectWordAction);
2205 createActions();
2206 }
2207
2208 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2209 start.actionPerformed(e);
2210 end.actionPerformed(e);
2211 }
2212
2213 protected void createActions() {
2214 start = new BeginWordAction("pigdog", false);
2215 end = new EndWordAction("pigdog", true);
2216 }
2217
2218 public final String getMacroID() {
2219 return DefaultEditorKit.selectWordAction;
2220 }
2221
2222 }
2223
2224
2225 /**
2226 * Puts the text area into read-only mode.
2227 */
2228 public static class SetReadOnlyAction extends RecordableTextAction {
2229
2230
2231 public SetReadOnlyAction() {
2232 super(readOnlyAction);
2233 }
2234
2235 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2236 textArea.setEditable(false);
2237 }
2238
2239 public final String getMacroID() {
2240 return DefaultEditorKit.readOnlyAction;
2241 }
2242
2243 public boolean isRecordable() {
2244 return false; // Why would you want to record this?
2245 }
2246
2247 }
2248
2249
2250 /**
2251 * Puts the text area into writable (from read-only) mode.
2252 */
2253 public static class SetWritableAction extends RecordableTextAction {
2254
2255
2256 public SetWritableAction() {
2257 super(writableAction);
2258 }
2259
2260 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2261 textArea.setEditable(true);
2262 }
2263
2264 public final String getMacroID() {
2265 return DefaultEditorKit.writableAction;
2266 }
2267
2268 public boolean isRecordable() {
2269 return false; // Why would you want to record this?
2270 }
2271
2272 }
2273
2274
2275 /**
2276 * The action for inserting a time/date stamp.
2277 */
2278 public static class TimeDateAction extends RecordableTextAction {
2279
2280
2281 public TimeDateAction() {
2282 super(rtaTimeDateAction);
2283 }
2284
2285 public TimeDateAction(String name, Icon icon, String desc,
2286 Integer mnemonic, KeyStroke accelerator) {
2287 super(name, icon, desc, mnemonic, accelerator);
2288 }
2289
2290 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2291 if (!textArea.isEditable() || !textArea.isEnabled()) {
2292 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
2293 return;
2294 }
2295 Date today = new Date();
2296 DateFormat timeDateStamp = DateFormat.getDateTimeInstance();
2297 String dateString = timeDateStamp.format(today);
2298 textArea.replaceSelection(dateString);
2299 }
2300
2301 public final String getMacroID() {
2302 return rtaTimeDateAction;
2303 }
2304
2305 }
2306
2307
2308 /**
2309 * Toggles whether the current line has a bookmark.
2310 */
2311 public static class ToggleBookmarkAction extends RecordableTextAction {
2312
2313 public ToggleBookmarkAction() {
2314 super(rtaToggleBookmarkAction);
2315 }
2316
2317 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2318 Gutter gutter = RSyntaxUtilities.getGutter(textArea);
2319 if (gutter!=null) {
2320 int line = textArea.getCaretLineNumber();
2321 try {
2322 gutter.toggleBookmark(line);
2323 } catch (BadLocationException ble) { // Never happens
2324 UIManager.getLookAndFeel().
2325 provideErrorFeedback(textArea);
2326 ble.printStackTrace();
2327 }
2328 }
2329 }
2330
2331 public final String getMacroID() {
2332 return rtaToggleBookmarkAction;
2333 }
2334
2335 }
2336
2337
2338 /**
2339 * The action for the insert key toggling insert/overwrite modes.
2340 */
2341 public static class ToggleTextModeAction extends RecordableTextAction {
2342
2343 public ToggleTextModeAction() {
2344 super(rtaToggleTextModeAction);
2345 }
2346
2347 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2348 int textMode = textArea.getTextMode();
2349 if (textMode==RTextArea.INSERT_MODE)
2350 textArea.setTextMode(RTextArea.OVERWRITE_MODE);
2351 else
2352 textArea.setTextMode(RTextArea.INSERT_MODE);
2353 }
2354
2355 public final String getMacroID() {
2356 return rtaToggleTextModeAction;
2357 }
2358
2359 }
2360
2361
2362 /**
2363 * Undoes the last action done.
2364 */
2365 public static class UndoAction extends RecordableTextAction {
2366
2367 public UndoAction() {
2368 super(rtaUndoAction);
2369 }
2370
2371 public UndoAction(String name, Icon icon, String desc,
2372 Integer mnemonic, KeyStroke accelerator) {
2373 super(name, icon, desc, mnemonic, accelerator);
2374 }
2375
2376 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2377 if (textArea.isEnabled() && textArea.isEditable()) {
2378 textArea.undoLastAction();
2379 textArea.requestFocusInWindow();
2380 }
2381 }
2382
2383 public final String getMacroID() {
2384 return rtaUndoAction;
2385 }
2386
2387 }
2388
2389
2390 /**
2391 * Removes the selection, if any.
2392 */
2393 public static class UnselectAction extends RecordableTextAction {
2394
2395
2396 public UnselectAction() {
2397 super(rtaUnselectAction);
2398 }
2399
2400 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2401 textArea.setCaretPosition(textArea.getCaretPosition());
2402 }
2403
2404 public final String getMacroID() {
2405 return rtaUnselectAction;
2406 }
2407
2408 }
2409
2410
2411 /**
2412 * Action to make the selection upper-case.
2413 */
2414 public static class UpperSelectionCaseAction extends RecordableTextAction {
2415
2416
2417 public UpperSelectionCaseAction() {
2418 super(rtaUpperSelectionCaseAction);
2419 }
2420
2421 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2422 if (!textArea.isEditable() || !textArea.isEnabled()) {
2423 UIManager.getLookAndFeel().provideErrorFeedback(textArea);
2424 return;
2425 }
2426 String selection = textArea.getSelectedText();
2427 if (selection!=null)
2428 textArea.replaceSelection(selection.toUpperCase());
2429 textArea.requestFocusInWindow();
2430 }
2431
2432 public final String getMacroID() {
2433 return getName();
2434 }
2435
2436 }
2437
2438
2439 /**
2440 * Scrolls up/down vertically. The select version of this action extends
2441 * the selection, instead of simply moving the caret.
2442 */
2443 public static class VerticalPageAction extends RecordableTextAction {
2444
2445 private boolean select;
2446 private int direction;
2447
2448 public VerticalPageAction(String name, int direction, boolean select) {
2449 super(name);
2450 this.select = select;
2451 this.direction = direction;
2452 }
2453
2454 public void actionPerformedImpl(ActionEvent e, RTextArea textArea) {
2455
2456 Rectangle visible = textArea.getVisibleRect();
2457 Rectangle newVis = new Rectangle(visible);
2458 int selectedIndex = textArea.getCaretPosition();
2459 int scrollAmount = textArea.getScrollableBlockIncrement(
2460 visible, SwingConstants.VERTICAL, direction);
2461 int initialY = visible.y;
2462 Caret caret = textArea.getCaret();
2463 Point magicPosition = caret.getMagicCaretPosition();
2464 int yOffset;
2465
2466 if (selectedIndex!=-1) {
2467
2468 try {
2469
2470 Rectangle dotBounds = textArea.modelToView(selectedIndex);
2471 int x = (magicPosition != null) ? magicPosition.x :
2472 dotBounds.x;
2473 int h = dotBounds.height;
2474 yOffset = direction *
2475 ((int)Math.ceil(scrollAmount/(double)h)-1)*h;
2476 newVis.y = constrainY(textArea, initialY+yOffset, yOffset, visible.height);
2477 int newIndex;
2478
2479 if (visible.contains(dotBounds.x, dotBounds.y)) {
2480 // Dot is currently visible, base the new
2481 // location off the old, or
2482 newIndex = textArea.viewToModel(
2483 new Point(x, constrainY(textArea,
2484 dotBounds.y + yOffset, 0, 0)));
2485 }
2486 else {
2487 // Dot isn't visible, choose the top or the bottom
2488 // for the new location.
2489 if (direction == -1) {
2490 newIndex = textArea.viewToModel(new Point(
2491 x, newVis.y));
2492 }
2493 else {
2494 newIndex = textArea.viewToModel(new Point(
2495 x, newVis.y + visible.height));
2496 }
2497 }
2498 newIndex = constrainOffset(textArea, newIndex);
2499 if (newIndex != selectedIndex) {
2500 // Make sure the new visible location contains
2501 // the location of dot, otherwise Caret will
2502 // cause an additional scroll.
2503 adjustScrollIfNecessary(textArea, newVis, initialY,
2504 newIndex);
2505 if (select)
2506 textArea.moveCaretPosition(newIndex);
2507 else
2508 textArea.setCaretPosition(newIndex);
2509 }
2510
2511 } catch (BadLocationException ble) { }
2512
2513 } // End of if (selectedIndex!=-1).
2514
2515 else {
2516 yOffset = direction * scrollAmount;
2517 newVis.y = constrainY(textArea, initialY + yOffset, yOffset, visible.height);
2518 }
2519
2520 if (magicPosition != null)
2521 caret.setMagicCaretPosition(magicPosition);
2522
2523 textArea.scrollRectToVisible(newVis);
2524 }
2525
2526 private int constrainY(JTextComponent textArea, int y, int vis, int screenHeight) {
2527 if (y < 0)
2528 y = 0;
2529 else if (y + vis > textArea.getHeight()) {
2530 //y = Math.max(0, textArea.getHeight() - vis);
2531 y = Math.max(0, textArea.getHeight()-screenHeight);
2532 }
2533 return y;
2534 }
2535
2536 private int constrainOffset(JTextComponent text, int offset) {
2537 Document doc = text.getDocument();
2538 if ((offset != 0) && (offset > doc.getLength()))
2539 offset = doc.getLength();
2540 if (offset < 0)
2541 offset = 0;
2542 return offset;
2543 }
2544
2545 private void adjustScrollIfNecessary(JTextComponent text,
2546 Rectangle visible, int initialY,
2547 int index) {
2548 try {
2549 Rectangle dotBounds = text.modelToView(index);
2550 if (dotBounds.y < visible.y ||
2551 (dotBounds.y > (visible.y + visible.height)) ||
2552 (dotBounds.y + dotBounds.height) >
2553 (visible.y + visible.height)) {
2554 int y;
2555 if (dotBounds.y < visible.y)
2556 y = dotBounds.y;
2557 else
2558 y = dotBounds.y + dotBounds.height - visible.height;
2559 if ((direction == -1 && y < initialY) ||
2560 (direction == 1 && y > initialY))
2561 // Only adjust if won't cause scrolling upward.
2562 visible.y = y;
2563 }
2564 } catch (BadLocationException ble) {}
2565 }
2566
2567 public final String getMacroID() {
2568 return getName();
2569 }
2570
2571 }
2572
2573
2574}
Note: See TracBrowser for help on using the repository browser.