source: other-projects/rsyntax-textarea/src/java/org/fife/ui/rtextarea/RTextArea.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: 47.1 KB
Line 
1/*
2 * 11/14/2003
3 *
4 * RTextArea.java - An extension of JTextArea that adds many features.
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.Color;
12import java.awt.ComponentOrientation;
13import java.awt.Graphics;
14import java.awt.Toolkit;
15import java.awt.event.ActionEvent;
16import java.awt.event.FocusEvent;
17import java.awt.event.KeyEvent;
18import java.awt.event.MouseEvent;
19import java.awt.print.PageFormat;
20import java.awt.print.Printable;
21import java.io.IOException;
22import java.io.ObjectInputStream;
23import java.io.ObjectOutputStream;
24import java.io.Reader;
25import java.io.Serializable;
26import java.util.ArrayList;
27import java.util.List;
28import java.util.Locale;
29import java.util.ResourceBundle;
30import javax.swing.Action;
31import javax.swing.Icon;
32import javax.swing.InputMap;
33import javax.swing.JMenuItem;
34import javax.swing.JPopupMenu;
35import javax.swing.KeyStroke;
36import javax.swing.SwingUtilities;
37import javax.swing.UIManager;
38import javax.swing.event.CaretEvent;
39import javax.swing.plaf.TextUI;
40import javax.swing.text.AbstractDocument;
41import javax.swing.text.BadLocationException;
42import javax.swing.text.Caret;
43import javax.swing.text.DefaultEditorKit;
44import javax.swing.text.Document;
45import javax.swing.text.Element;
46import javax.swing.text.Highlighter;
47import javax.swing.undo.CannotRedoException;
48import javax.swing.undo.CannotUndoException;
49
50import org.fife.print.RPrintUtilities;
51import org.fife.ui.rtextarea.Macro.MacroRecord;
52
53
54/**
55 * An extension of <code>JTextArea</code> that adds the following features:
56 * <ul>
57 * <li>Insert/Overwrite modes (can be toggled via the Insert key)
58 * <li>A right-click popup menu with standard editing options
59 * <li>Macro support
60 * <li>"Mark all" functionality.
61 * <li>A way to change the background to an image (gif/png/jpg)
62 * <li>Highlight the current line (can be toggled)
63 * <li>An easy way to print its text (implements Printable)
64 * <li>Hard/soft (emulated with spaces) tabs
65 * <li>Fixes a bug with setTabSize
66 * <li>Other handy new methods
67 * </ul>
68 * NOTE: If the background for an <code>RTextArea</code> is set to a color,
69 * its opaque property is set to <code>true</code> for performance reasons. If
70 * the background is set to an image, then the opaque property is set to
71 * <code>false</code>. This slows things down a little, but if it didn't happen
72 * then we would see garbage on-screen when the user scrolled through a document
73 * using the arrow keys (not the page-up/down keys though). You should never
74 * have to set the opaque property yourself; it is always done for you.
75 *
76 * @author Robert Futrell
77 * @version 1.0
78 */
79public class RTextArea extends RTextAreaBase
80 implements Printable, Serializable {
81
82 /**
83 * Constant representing insert mode.
84 *
85 * @see #setCaretStyle(int, int)
86 */
87 public static final int INSERT_MODE = 0;
88
89 /**
90 * Constant representing overwrite mode.
91 *
92 * @see #setCaretStyle(int, int)
93 */
94 public static final int OVERWRITE_MODE = 1;
95
96 /**
97 * The property fired when the "mark all" color changes.
98 */
99 public static final String MARK_ALL_COLOR_PROPERTY = "RTA.markAllColor";
100
101 /*
102 * Constants for all actions.
103 */
104 private static final int MIN_ACTION_CONSTANT = 0;
105 public static final int COPY_ACTION = 0;
106 public static final int CUT_ACTION = 1;
107 public static final int DELETE_ACTION = 2;
108 public static final int PASTE_ACTION = 3;
109 public static final int REDO_ACTION = 4;
110 public static final int SELECT_ALL_ACTION = 5;
111 public static final int UNDO_ACTION = 6;
112 private static final int MAX_ACTION_CONSTANT = 6;
113
114 private static final Color DEFAULT_MARK_ALL_COLOR = Color.ORANGE;
115
116 /**
117 * The current text mode ({@link #INSERT_MODE} or {@link #OVERWRITE_MODE}).
118 */
119 private int textMode;
120
121 // All macros are shared across all RTextAreas.
122 private static boolean recordingMacro; // Whether we're recording a macro.
123 private static Macro currentMacro;
124
125 /**
126 * This text area's popup menu.
127 */
128 private JPopupMenu popupMenu;
129
130 /**
131 * Whether the popup menu has been created.
132 */
133 private boolean popupMenuCreated;
134
135 /**
136 * The text last searched for via Ctrl+K or Ctrl+Shift+K.
137 */
138 private static String selectedOccurrenceText;
139
140 /**
141 * Can return tool tips for this text area. Subclasses can install a
142 * supplier as a means of adding custom tool tips without subclassing
143 * <tt>RTextArea</tt>. {@link #getToolTipText()} checks this supplier
144 * before calling the super class's version.
145 */
146 private ToolTipSupplier toolTipSupplier;
147
148 private static RecordableTextAction cutAction;
149 private static RecordableTextAction copyAction;
150 private static RecordableTextAction pasteAction;
151 private static RecordableTextAction deleteAction;
152 private static RecordableTextAction undoAction;
153 private static RecordableTextAction redoAction;
154 private static RecordableTextAction selectAllAction;
155
156 private static IconGroup iconGroup; // Info on icons for actions.
157
158 private transient RUndoManager undoManager;
159
160 private transient LineHighlightManager lineHighlightManager;
161
162 private ArrayList markAllHighlights; // Highlights from "mark all".
163 private String markedWord; // Expression marked in "mark all."
164 private ChangeableHighlightPainter markAllHighlightPainter;
165
166 private int[] carets; // Index 0=>insert caret, 1=>overwrite.
167
168 private static final String MSG = "org.fife.ui.rtextarea.RTextArea";
169
170
171 /**
172 * Constructor.
173 */
174 public RTextArea() {
175 init(INSERT_MODE);
176 }
177
178
179 /**
180 * Constructor.
181 *
182 * @param doc The document for the editor.
183 */
184 public RTextArea(AbstractDocument doc) {
185 super(doc);
186 init(INSERT_MODE);
187 }
188
189
190 /**
191 * Constructor.
192 *
193 * @param text The initial text to display.
194 */
195 public RTextArea(String text) {
196 super(text);
197 init(INSERT_MODE);
198 }
199
200
201 /**
202 * Constructor.
203 *
204 * @param rows The number of rows to display.
205 * @param cols The number of columns to display.
206 * @throws IllegalArgumentException If either <code>rows</code> or
207 * <code>cols</code> is negative.
208 */
209 public RTextArea(int rows, int cols) {
210 super(rows, cols);
211 init(INSERT_MODE);
212 }
213
214
215 /**
216 * Constructor.
217 *
218 * @param text The initial text to display.
219 * @param rows The number of rows to display.
220 * @param cols The number of columns to display.
221 * @throws IllegalArgumentException If either <code>rows</code> or
222 * <code>cols</code> is negative.
223 */
224 public RTextArea(String text, int rows, int cols) {
225 super(text, rows, cols);
226 init(INSERT_MODE);
227 }
228
229
230 /**
231 * Constructor.
232 *
233 * @param doc The document for the editor.
234 * @param text The initial text to display.
235 * @param rows The number of rows to display.
236 * @param cols The number of columns to display.
237 * @throws IllegalArgumentException If either <code>rows</code> or
238 * <code>cols</code> is negative.
239 */
240 public RTextArea(AbstractDocument doc, String text, int rows, int cols) {
241 super(doc, text, rows, cols);
242 init(INSERT_MODE);
243 }
244
245
246 /**
247 * Creates a new <code>RTextArea</code>.
248 *
249 * @param textMode Either <code>INSERT_MODE</code> or
250 * <code>OVERWRITE_MODE</code>.
251 */
252 public RTextArea(int textMode) {
253 init(textMode);
254 }
255
256
257 /**
258 * Adds an action event to the current macro. This shouldn't be called
259 * directly, as it is called by the actions themselves.
260 *
261 * @param id The ID of the recordable text action.
262 * @param actionCommand The "command" of the action event passed to it.
263 */
264 static synchronized void addToCurrentMacro(String id,
265 String actionCommand) {
266 currentMacro.addMacroRecord(new Macro.MacroRecord(id, actionCommand));
267 }
268
269
270 /**
271 * Adds a line highlight.
272 *
273 * @param line The line to highlight. This is zero-based.
274 * @param color The color to highlight the line with.
275 * @throws BadLocationException If <code>line</code> is an invalid line
276 * number.
277 * @see #removeLineHighlight(Object)
278 * @see #removeAllLineHighlights()
279 */
280 public Object addLineHighlight(int line, Color color)
281 throws BadLocationException {
282 if (lineHighlightManager==null) {
283 lineHighlightManager = new LineHighlightManager(this);
284 }
285 return lineHighlightManager.addLineHighlight(line, color);
286 }
287
288
289 /**
290 * Begins an "atomic edit." All text editing operations between this call
291 * and the next call to <tt>endAtomicEdit()</tt> will be treated as a
292 * single operation by the undo manager.<p>
293 *
294 * Using this method should be done with great care. You should probably
295 * wrap the call to <tt>endAtomicEdit()</tt> in a <tt>finally</tt> block:
296 *
297 * <pre>
298 * textArea.beginAtomicEdit();
299 * try {
300 * // Do editing
301 * } finally {
302 * textArea.endAtomicEdit();
303 * }
304 * </pre>
305 *
306 * @see #endAtomicEdit()
307 */
308 public void beginAtomicEdit() {
309 undoManager.beginInternalAtomicEdit();
310 }
311
312
313 /**
314 * Begins recording a macro. After this method is called, all input/caret
315 * events, etc. are recorded until <code>endMacroRecording</code> is
316 * called. If this method is called but the text component is already
317 * recording a macro, nothing happens (but the macro keeps recording).
318 *
319 * @see #isRecordingMacro()
320 * @see #endRecordingMacro()
321 */
322 public static synchronized void beginRecordingMacro() {
323 if (isRecordingMacro()) {
324 //System.err.println("Macro already being recorded!");
325 return;
326 }
327 //JOptionPane.showMessageDialog(this, "Now recording a macro");
328 if (currentMacro!=null)
329 currentMacro = null; // May help gc?
330 currentMacro = new Macro();
331 recordingMacro = true;
332 }
333
334
335 /**
336 * Tells whether an undo is possible
337 *
338 * @see #canRedo()
339 * @see #undoLastAction()
340 */
341 public boolean canUndo() {
342 return undoManager.canUndo();
343 }
344
345
346 /**
347 * Tells whether a redo is possible
348 *
349 * @see #canUndo()
350 * @see #redoLastAction()
351 */
352 public boolean canRedo() {
353 return undoManager.canRedo();
354 }
355
356
357 /**
358 * Clears any "mark all" highlights, if any.
359 *
360 * @see #markAll
361 * @see #getMarkAllHighlightColor
362 * @see #setMarkAllHighlightColor
363 */
364 public void clearMarkAllHighlights() {
365 Highlighter h = getHighlighter();
366 if (h!=null && markAllHighlights!=null) {
367 int count = markAllHighlights.size();
368 for (int i=0; i<count; i++)
369 h.removeHighlight(markAllHighlights.get(i));
370 markAllHighlights.clear();
371 }
372 markedWord = null;
373 repaint();
374 }
375
376
377 /**
378 * Configures the popup menu for this text area. This method is called
379 * right before it is displayed, so a hosting application can do any
380 * custom configuration (configuring actions, adding/removing items, etc.).
381 * <p>
382 *
383 * The default implementation does nothing.<p>
384 *
385 * If you set the popup menu via {@link #setPopupMenu(JPopupMenu)}, you
386 * will want to override this method, especially if you removed any of the
387 * menu items in the default popup menu.
388 *
389 * @param popupMenu The popup menu. This will never be <code>null</code>.
390 * @see #createPopupMenu()
391 * @see #setPopupMenu(JPopupMenu)
392 */
393 protected void configurePopupMenu(JPopupMenu popupMenu) {
394 }
395
396
397 /**
398 * Returns the caret event/mouse listener for <code>RTextArea</code>s.
399 *
400 * @return The caret event/mouse listener.
401 */
402 protected RTAMouseListener createMouseListener() {
403 return new RTextAreaMutableCaretEvent(this);
404 }
405
406
407 /**
408 * Creates the right-click popup menu. Subclasses can override this method
409 * to replace or augment the popup menu returned.
410 *
411 * @return The popup menu.
412 * @see #setPopupMenu(JPopupMenu)
413 * @see #configurePopupMenu(JPopupMenu)
414 * @see #createPopupMenuItem(Action)
415 */
416 protected JPopupMenu createPopupMenu() {
417 JPopupMenu menu = new JPopupMenu();
418 menu.add(createPopupMenuItem(undoAction));
419 menu.add(createPopupMenuItem(redoAction));
420 menu.addSeparator();
421 menu.add(createPopupMenuItem(cutAction));
422 menu.add(createPopupMenuItem(copyAction));
423 menu.add(createPopupMenuItem(pasteAction));
424 menu.add(createPopupMenuItem(deleteAction));
425 menu.addSeparator();
426 menu.add(createPopupMenuItem(selectAllAction));
427 return menu;
428 }
429
430
431 /**
432 * Creates the actions used in the popup menu and retrievable by
433 * {@link #getAction(int)}.
434 * TODO: Remove these horrible hacks and move localizing of actions into
435 * the editor kits, where it should be! The context menu should contain
436 * actions from the editor kits.
437 */
438 private static void createPopupMenuActions() {
439
440 // Create actions for right-click popup menu.
441 // 1.5.2004/pwy: Replaced the CTRL_MASK with the cross-platform version...
442 int mod = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
443 ResourceBundle msg = ResourceBundle.getBundle(MSG);
444
445 cutAction = new RTextAreaEditorKit.CutAction();
446 cutAction.setProperties(msg, "Action.Cut");
447 cutAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, mod));
448 copyAction = new RTextAreaEditorKit.CopyAction();
449 copyAction.setProperties(msg, "Action.Copy");
450 copyAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, mod));
451 pasteAction = new RTextAreaEditorKit.PasteAction();
452 pasteAction.setProperties(msg, "Action.Paste");
453 pasteAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, mod));
454 deleteAction = new RTextAreaEditorKit.DeleteNextCharAction();
455 deleteAction.setProperties(msg, "Action.Delete");
456 deleteAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
457 undoAction = new RTextAreaEditorKit.UndoAction();
458 undoAction.setProperties(msg, "Action.Undo");
459 undoAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mod));
460 redoAction = new RTextAreaEditorKit.RedoAction();
461 redoAction.setProperties(msg, "Action.Redo");
462 redoAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, mod));
463 selectAllAction = new RTextAreaEditorKit.SelectAllAction();
464 selectAllAction.setProperties(msg, "Action.SelectAll");
465 selectAllAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, mod));
466
467 }
468
469
470 /**
471 * Creates and configures a menu item for used in the popup menu.
472 *
473 * @param a The action for the menu item.
474 * @return The menu item.
475 * @see #createPopupMenu()
476 */
477 protected JMenuItem createPopupMenuItem(Action a) {
478 JMenuItem item = new JMenuItem(a) {
479 public void setToolTipText(String text) {
480 // Ignore! Actions (e.g. undo/redo) set this when changing
481 // their text due to changing enabled state.
482 }
483 };
484 item.setAccelerator(null);
485 return item;
486 }
487
488
489 /**
490 * Returns the a real UI to install on this text area.
491 *
492 * @return The UI.
493 */
494 protected RTextAreaUI createRTextAreaUI() {
495 return new RTextAreaUI(this);
496 }
497
498
499 /**
500 * Creates an undo manager for use in this text area.
501 *
502 * @return The undo manager.
503 */
504 protected RUndoManager createUndoManager() {
505 return new RUndoManager(this);
506 }
507
508
509 /**
510 * Removes all undoable edits from this document's undo manager. This
511 * method also makes the undo/redo actions disabled.
512 */
513 /*
514 * NOTE: For some reason, it appears I have to create an entirely new
515 * <code>undoManager</code> for undo/redo to continue functioning
516 * properly; if I don't, it only ever lets you do one undo. Not
517 * too sure why this is...
518 */
519 public void discardAllEdits() {
520 undoManager.discardAllEdits();
521 getDocument().removeUndoableEditListener(undoManager);
522 undoManager = createUndoManager();
523 getDocument().addUndoableEditListener(undoManager);
524 undoManager.updateActions();
525 }
526
527
528 /**
529 * Completes an "atomic" edit.
530 *
531 * @see #beginAtomicEdit()
532 */
533 public void endAtomicEdit() {
534 undoManager.endInternalAtomicEdit();
535 }
536
537
538 /**
539 * Ends recording a macro. If this method is called but the text component
540 * is not recording a macro, nothing happens.
541 *
542 * @see #isRecordingMacro()
543 * @see #beginRecordingMacro()
544 */
545 /*
546 * FIXME: This should throw an exception if we're not recording a macro.
547 */
548 public static synchronized void endRecordingMacro() {
549 if (!isRecordingMacro()) {
550 //System.err.println("Not recording a macro!");
551 return;
552 }
553 recordingMacro = false;
554 }
555
556
557 /**
558 * Notifies all listeners that a caret change has occurred.
559 *
560 * @param e The caret event.
561 */
562 protected void fireCaretUpdate(CaretEvent e) {
563
564 // Decide whether we need to repaint the current line background.
565 possiblyUpdateCurrentLineHighlightLocation();
566
567 // Now, if there is a highlighted region of text, allow them to cut
568 // and copy.
569 if (e!=null && e.getDot()!=e.getMark()) {// && !cutAction.isEnabled()) {
570 cutAction.setEnabled(true);
571 copyAction.setEnabled(true);
572 }
573
574 // Otherwise, if there is no highlighted region, don't let them cut
575 // or copy. The condition here should speed things up, because this
576 // way, we will only enable the actions the first time the selection
577 // becomes nothing.
578 else if (cutAction.isEnabled()) {
579 cutAction.setEnabled(false);
580 copyAction.setEnabled(false);
581 }
582
583 super.fireCaretUpdate(e);
584
585 }
586
587
588 /**
589 * Removes the "Ctrl+H <=> Backspace" behavior that Java shows, for some
590 * odd reason...
591 */
592 private void fixCtrlH() {
593 InputMap inputMap = getInputMap();
594 KeyStroke char010 = KeyStroke.getKeyStroke("typed \010");
595 InputMap parent = inputMap;
596 while (parent != null) {
597 parent.remove(char010);
598 parent = parent.getParent();
599 }
600 KeyStroke backspace = KeyStroke.getKeyStroke("BACK_SPACE");
601 inputMap.put(backspace, DefaultEditorKit.deletePrevCharAction);
602 }
603
604
605 /**
606 * Provides a way to gain access to the editor actions on the right-click
607 * popup menu. This way you can make toolbar/menu bar items use the actual
608 * actions used by all <code>RTextArea</code>s, so that icons stay
609 * synchronized and you don't have to worry about enabling/disabling them
610 * yourself.<p>
611 * Keep in mind that these actions are shared across all instances of
612 * <code>RTextArea</code>, so a change to any action returned by this
613 * method is global across all <code>RTextArea</code> editors in your
614 * application.
615 *
616 * @param action The action to retrieve, such as {@link #CUT_ACTION}.
617 * If the action name is invalid, <code>null</code> is returned.
618 * @return The action, or <code>null</code> if an invalid action is
619 * requested.
620 */
621 public static RecordableTextAction getAction(int action) {
622 if (action<MIN_ACTION_CONSTANT || action>MAX_ACTION_CONSTANT)
623 return null;
624 switch (action) {
625 case COPY_ACTION:
626 return copyAction;
627 case CUT_ACTION:
628 return cutAction;
629 case DELETE_ACTION:
630 return deleteAction;
631 case PASTE_ACTION:
632 return pasteAction;
633 case REDO_ACTION:
634 return redoAction;
635 case SELECT_ALL_ACTION:
636 return selectAllAction;
637 case UNDO_ACTION:
638 return undoAction;
639 }
640 return null;
641 }
642
643
644 /**
645 * Returns the macro currently stored in this <code>RTextArea</code>.
646 * Since macros are shared, all <code>RTextArea</code>s in the currently-
647 * running application are using this macro.
648 *
649 * @return The current macro, or <code>null</code> if no macro has been
650 * recorded/loaded.
651 * @see #loadMacro(Macro)
652 */
653 public static synchronized Macro getCurrentMacro() {
654 return currentMacro;
655 }
656
657
658 /**
659 * Returns the default color used for "mark all."
660 *
661 * @return The color.
662 * @see #getMarkAllHighlightColor()
663 * @see #setMarkAllHighlightColor(Color)
664 */
665 public static final Color getDefaultMarkAllHighlightColor() {
666 return DEFAULT_MARK_ALL_COLOR;
667 }
668
669
670 /**
671 * Returns the icon group being used for the actions of this text area.
672 *
673 * @return The icon group.
674 * @see #setIconGroup(IconGroup)
675 */
676 public static IconGroup getIconGroup() {
677 return iconGroup;
678 }
679
680
681 /**
682 * Returns the line highlight manager.
683 *
684 * @return The line highlight manager. This may be <code>null</code>.
685 */
686 LineHighlightManager getLineHighlightManager() {
687 return lineHighlightManager;
688 }
689
690
691 /**
692 * Returns the color used in "mark all."
693 *
694 * @return The color.
695 * @see #setMarkAllHighlightColor(Color)
696 */
697 public Color getMarkAllHighlightColor() {
698 return (Color)markAllHighlightPainter.getPaint();
699 }
700
701
702 /**
703 * Returns the maximum ascent of all fonts used in this text area. In
704 * the case of a standard <code>RTextArea</code>, this is simply the
705 * ascent of the current font.<p>
706 *
707 * This value could be useful, for example, to implement a line-numbering
708 * scheme.
709 *
710 * @return The ascent of the current font.
711 */
712 public int getMaxAscent() {
713 return getFontMetrics(getFont()).getAscent();
714 }
715
716
717 /**
718 * Returns the popup menu for this component, lazily creating it if
719 * necessary.
720 *
721 * @return The popup menu.
722 * @see #createPopupMenu()
723 * @see #setPopupMenu(JPopupMenu)
724 */
725 public JPopupMenu getPopupMenu() {
726 if (!popupMenuCreated) {
727 popupMenu = createPopupMenu();
728 if (popupMenu!=null) {
729 ComponentOrientation orientation = ComponentOrientation.
730 getOrientation(Locale.getDefault());
731 popupMenu.applyComponentOrientation(orientation);
732 }
733 popupMenuCreated = true;
734 }
735 return popupMenu;
736 }
737
738
739 /**
740 * Returns the text last selected and used in a Ctrl+K operation.
741 *
742 * @return The text, or <code>null</code> if none.
743 * @see #setSelectedOccurrenceText(String)
744 */
745 public static String getSelectedOccurrenceText() {
746 return selectedOccurrenceText;
747 }
748
749
750 /**
751 * Returns the text mode this editor pane is currently in.
752 *
753 * @return Either {@link #INSERT_MODE} or {@link #OVERWRITE_MODE}.
754 * @see #setTextMode(int)
755 */
756 public final int getTextMode() {
757 return textMode;
758 }
759
760
761 /**
762 * Returns the tool tip supplier.
763 *
764 * @return The tool tip supplier, or <code>null</code> if one isn't
765 * installed.
766 * @see #setToolTipSupplier(ToolTipSupplier)
767 */
768 public ToolTipSupplier getToolTipSupplier() {
769 return toolTipSupplier;
770 }
771
772
773 /**
774 * Returns the tooltip to display for a mouse event at the given
775 * location. This method is overridden to check for a
776 * {@link ToolTipSupplier}; if there is one installed, it is queried for
777 * tool tip text before using the super class's implementation of this
778 * method.
779 *
780 * @param e The mouse event.
781 * @return The tool tip text, or <code>null</code> if none.
782 * @see #getToolTipSupplier()
783 * @see #setToolTipSupplier(ToolTipSupplier)
784 */
785 public String getToolTipText(MouseEvent e) {
786 String tip = null;
787 if (getToolTipSupplier()!=null) {
788 tip = getToolTipSupplier().getToolTipText(this, e);
789 }
790 return tip!=null ? tip : super.getToolTipText();
791 }
792
793
794 /**
795 * Does the actual dirty-work of replacing the selected text in this
796 * text area (i.e., in its document). This method provides a hook for
797 * subclasses to handle this in a different way.
798 *
799 * @param content The content to add.
800 */
801 protected void handleReplaceSelection(String content) {
802 // Call into super to handle composed text (1.5+ only though).
803 super.replaceSelection(content);
804 }
805
806
807 /**
808 * Initializes this text area.
809 *
810 * @param textMode The text mode.
811 */
812 private void init(int textMode) {
813
814 // NOTE: Our actions are created here instead of in a static block
815 // so they are only created when the first RTextArea is instantiated,
816 // not before. There have been reports of users calling static getters
817 // (e.g. RSyntaxTextArea.getDefaultBracketMatchBGColor()) which would
818 // cause these actions to be created and (possibly) incorrectly
819 // localized, if they were in a static block.
820 if (cutAction==null) {
821 createPopupMenuActions();
822 }
823
824 // Install the undo manager.
825 undoManager = createUndoManager();
826 getDocument().addUndoableEditListener(undoManager);
827
828 // Set the defaults for various stuff.
829 Color markAllHighlightColor = getDefaultMarkAllHighlightColor();
830 markAllHighlightPainter = new ChangeableHighlightPainter(
831 markAllHighlightColor);
832 setMarkAllHighlightColor(markAllHighlightColor);
833 carets = new int[2];
834 setCaretStyle(INSERT_MODE, ConfigurableCaret.THICK_VERTICAL_LINE_STYLE);
835 setCaretStyle(OVERWRITE_MODE, ConfigurableCaret.BLOCK_STYLE);
836 setDragEnabled(true); // Enable drag-and-drop.
837
838 // Set values for stuff the user passed in.
839 setTextMode(textMode); // carets array must be initialized first!
840
841 // Fix the odd "Ctrl+H <=> Backspace" Java behavior.
842 fixCtrlH();
843
844 }
845
846
847 /**
848 * Returns whether or not a macro is being recorded.
849 *
850 * @return Whether or not a macro is being recorded.
851 * @see #beginRecordingMacro()
852 * @see #endRecordingMacro()
853 */
854 public static synchronized boolean isRecordingMacro() {
855 return recordingMacro;
856 }
857
858
859 /**
860 * Loads a macro to be used by all <code>RTextArea</code>s in the current
861 * application.
862 *
863 * @param macro The macro to load.
864 * @see #getCurrentMacro()
865 */
866 public static synchronized void loadMacro(Macro macro) {
867 currentMacro = macro;
868 }
869
870
871 /**
872 * Marks all instances of the specified text in this text area.
873 *
874 * @param toMark The text to mark.
875 * @param matchCase Whether the match should be case-sensitive.
876 * @param wholeWord Whether the matches should be surrounded by spaces
877 * or tabs.
878 * @param regex Whether <code>toMark</code> is a Java regular expression.
879 * @return The number of matches marked.
880 * @see #clearMarkAllHighlights
881 * @see #getMarkAllHighlightColor
882 * @see #setMarkAllHighlightColor
883 */
884 public int markAll(String toMark, boolean matchCase, boolean wholeWord,
885 boolean regex) {
886 Highlighter h = getHighlighter();
887 int numMarked = 0;
888 if (toMark!=null && !toMark.equals(markedWord) && h!=null) {
889 if (markAllHighlights!=null)
890 clearMarkAllHighlights();
891 else
892 markAllHighlights = new ArrayList(10);
893 int caretPos = getCaretPosition();
894 markedWord = toMark;
895 setCaretPosition(0);
896 SearchContext context = new SearchContext();
897 context.setSearchFor(toMark);
898 context.setMatchCase(matchCase);
899 context.setRegularExpression(regex);
900 context.setSearchForward(true);
901 context.setWholeWord(wholeWord);
902 boolean found = SearchEngine.find(this, context);
903 while (found) {
904 int start = getSelectionStart();
905 int end = getSelectionEnd();
906 try {
907 markAllHighlights.add(h.addHighlight(start, end,
908 markAllHighlightPainter));
909 } catch (BadLocationException ble) {
910 ble.printStackTrace();
911 }
912 numMarked++;
913 found = SearchEngine.find(this, context);
914 }
915 setCaretPosition(caretPos);
916 repaint();
917 }
918 return numMarked;
919 }
920
921
922 /**
923 * {@inheritDoc}
924 */
925 public void paste() {
926 // Treat paste operations as atomic, otherwise the removal and
927 // insertion are treated as two separate undo-able operations.
928 beginAtomicEdit();
929 try {
930 super.paste();
931 } finally {
932 endAtomicEdit();
933 }
934 }
935
936
937 /**
938 * "Plays back" the last recorded macro in this text area.
939 */
940 public synchronized void playbackLastMacro() {
941 if (currentMacro!=null) {
942 Action[] actions = getActions();
943 int numActions = actions.length;
944 List macroRecords = currentMacro.getMacroRecords();
945 int num = macroRecords.size();
946 if (num>0) {
947 undoManager.beginInternalAtomicEdit();
948 try {
949 for (int i=0; i<num; i++) {
950 MacroRecord record = (MacroRecord)macroRecords.get(i);
951 for (int j=0; j<numActions; j++) {
952 if ((actions[j] instanceof RecordableTextAction) &&
953 record.id.equals(
954 ((RecordableTextAction)actions[j]).getMacroID())) {
955 actions[j].actionPerformed(
956 new ActionEvent(this,
957 ActionEvent.ACTION_PERFORMED,
958 record.actionCommand));
959 break;
960 }
961 }
962 }
963 } finally {
964 undoManager.endInternalAtomicEdit();
965 }
966 }
967 }
968 }
969
970
971 /**
972 * Method called when it's time to print this badboy (the old-school,
973 * AWT way).
974 *
975 * @param g The context into which the page is drawn.
976 * @param pageFormat The size and orientation of the page being drawn.
977 * @param pageIndex The zero based index of the page to be drawn.
978 */
979 public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
980 return RPrintUtilities.printDocumentWordWrap(g, this, getFont(), pageIndex, pageFormat, getTabSize());
981 }
982
983
984 /**
985 * We override this method because the super version gives us an entirely
986 * new <code>Document</code>, thus requiring us to re-attach our Undo
987 * manager. With this version we just replace the text.
988 */
989 public void read(Reader in, Object desc) throws IOException {
990
991 RTextAreaEditorKit kit = (RTextAreaEditorKit)getUI().getEditorKit(this);
992 setText(null);
993 Document doc = getDocument();
994 if (desc != null)
995 doc.putProperty(Document.StreamDescriptionProperty, desc);
996 try {
997 // NOTE: Resets the "line separator" property.
998 kit.read(in, doc, 0);
999 } catch (BadLocationException e) {
1000 throw new IOException(e.getMessage());
1001 }
1002
1003 }
1004
1005
1006 /**
1007 * De-serializes a text area.
1008 *
1009 * @param s The stream to read from.
1010 * @throws ClassNotFoundException
1011 * @throws IOException
1012 */
1013 private void readObject(ObjectInputStream s)
1014 throws ClassNotFoundException, IOException {
1015
1016 s.defaultReadObject();
1017
1018 // UndoManagers cannot be serialized without Exceptions. See
1019 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4275892
1020 undoManager = createUndoManager();
1021 getDocument().addUndoableEditListener(undoManager);
1022
1023 lineHighlightManager = null; // Keep FindBugs happy.
1024
1025 }
1026
1027
1028 /**
1029 * Attempt to redo the last action.
1030 *
1031 * @see #undoLastAction()
1032 */
1033 public void redoLastAction() {
1034 // NOTE: The try/catch block shouldn't be necessary...
1035 try {
1036 if (undoManager.canRedo())
1037 undoManager.redo();
1038 } catch (CannotRedoException cre) {
1039 cre.printStackTrace();
1040 }
1041 }
1042
1043
1044 /**
1045 * Removes all line highlights.
1046 *
1047 * @see #removeLineHighlight(Object)
1048 */
1049 public void removeAllLineHighlights() {
1050 if (lineHighlightManager!=null) {
1051 lineHighlightManager.removeAllLineHighlights();
1052 }
1053 }
1054
1055
1056 /**
1057 * Removes a line highlight.
1058 *
1059 * @param tag The tag of the line highlight to remove.
1060 * @see #removeAllLineHighlights()
1061 * @see #addLineHighlight(int, Color)
1062 */
1063 public void removeLineHighlight(Object tag) {
1064 if (lineHighlightManager!=null) {
1065 lineHighlightManager.removeLineHighlight(tag);
1066 }
1067 }
1068
1069
1070 /**
1071 * Replaces text from the indicated start to end position with the
1072 * new text specified. Does nothing if the model is null. Simply
1073 * does a delete if the new string is null or empty.
1074 * <p>
1075 * This method is thread safe, although most Swing methods
1076 * are not.<p>
1077 * This method is overridden so that our Undo manager remembers it as a
1078 * single operation (it has trouble with this, especially for
1079 * <code>RSyntaxTextArea</code> and the "auto-indent" feature).
1080 *
1081 * @param str the text to use as the replacement
1082 * @param start the start position >= 0
1083 * @param end the end position >= start
1084 * @exception IllegalArgumentException if part of the range is an
1085 * invalid position in the model
1086 * @see #insert(String, int)
1087 * @see #replaceRange(String, int, int)
1088 */
1089 public void replaceRange(String str, int start, int end) {
1090 if (end < start)
1091 throw new IllegalArgumentException("end before start");
1092 Document doc = getDocument();
1093 if (doc != null) {
1094 try {
1095 // Without this, in some cases we'll have to do two undos
1096 // for one logical operation (for example, try editing a
1097 // Java source file in an RSyntaxTextArea, and moving a line
1098 // with text already on it down via Enter. Without this
1099 // line, doing a single "undo" moves all later text up,
1100 // but the first line moved down isn't there! Doing a
1101 // second undo puts it back.
1102 undoManager.beginInternalAtomicEdit();
1103 ((AbstractDocument)doc).replace(start, end - start,
1104 str, null);
1105 } catch (BadLocationException e) {
1106 throw new IllegalArgumentException(e.getMessage());
1107 } finally {
1108 undoManager.endInternalAtomicEdit();
1109 }
1110 }
1111 }
1112
1113
1114 /**
1115 * This method overrides <code>JTextComponent</code>'s
1116 * <code>replaceSelection</code>, so that if <code>textMode</code> is
1117 * {@link #OVERWRITE_MODE}, it actually overwrites.
1118 *
1119 * @param text The content to replace the selection with.
1120 */
1121 public void replaceSelection(String text) {
1122
1123 // It's legal for null to be used here...
1124 if (text==null) {
1125 handleReplaceSelection(text);
1126 return;
1127 }
1128
1129 if (getTabsEmulated() && text.indexOf('\t')>-1) {
1130 text = replaceTabsWithSpaces(text);
1131 }
1132
1133 // If the user wants to overwrite text...
1134 if (textMode==OVERWRITE_MODE && !"\n".equals(text)) {
1135
1136 Caret caret = getCaret();
1137 int caretPos = caret.getDot();
1138 Document doc = getDocument();
1139 Element map = doc.getDefaultRootElement();
1140 int curLine = map.getElementIndex(caretPos);
1141 int lastLine = map.getElementCount() - 1;
1142
1143 try {
1144
1145 // If we're not at the end of a line, select the characters
1146 // that will be overwritten (otherwise JTextArea will simply
1147 // insert in front of them).
1148 int curLineEnd = getLineEndOffset(curLine);
1149 if (caretPos==caret.getMark() && caretPos!=curLineEnd) {
1150 if (curLine==lastLine)
1151 caretPos = Math.min(caretPos+text.length(), curLineEnd);
1152 else
1153 caretPos = Math.min(caretPos+text.length(), curLineEnd-1);
1154 caret.moveDot(caretPos);//moveCaretPosition(caretPos);
1155 }
1156
1157 } catch (BadLocationException ble) { // Never happens
1158 UIManager.getLookAndFeel().provideErrorFeedback(this);
1159 ble.printStackTrace();
1160 }
1161
1162 } // End of if (textMode==OVERWRITE_MODE).
1163
1164 // Now, actually do the inserting/replacing. Our undoManager will
1165 // take care of remembering the remove/insert as atomic if we are in
1166 // overwrite mode.
1167 handleReplaceSelection(text);
1168
1169 }
1170
1171
1172 private StringBuffer repTabsSB;
1173 /**
1174 * Replaces all instances of the tab character in <code>text</code> with
1175 * the number of spaces equivalent to a tab in this text area.<p>
1176 *
1177 * This method should only be called from thread-safe methods, such as
1178 * {@link #replaceSelection(String)}.
1179 *
1180 * @param text The <code>java.lang.String</code> in which to replace tabs
1181 * with spaces. This has already been verified to have at least
1182 * one tab character in it.
1183 * @return A <code>java.lang.String</code> just like <code>text</code>,
1184 * but with spaces instead of tabs.
1185 */
1186 private final String replaceTabsWithSpaces(String text) {
1187
1188 String tabText = "";
1189 int temp = getTabSize();
1190 for (int i=0; i<temp; i++) {
1191 tabText += ' ';
1192 }
1193
1194 // Common case: User's entering a single tab (pressed the tab key).
1195 if (text.length()==1) {
1196 return tabText;
1197 }
1198
1199 // Otherwise, there may be more than one tab. Manually search for
1200 // tabs for performance, as opposed to using String#replaceAll().
1201 // This method is called for each character inserted when "replace
1202 // tabs with spaces" is enabled, so we need to be quick.
1203
1204 //return text.replaceAll("\t", tabText);
1205 if (repTabsSB==null) {
1206 repTabsSB = new StringBuffer();
1207 }
1208 repTabsSB.setLength(0);
1209 char[] array = text.toCharArray(); // Wouldn't be needed in 1.5!
1210 int oldPos = 0;
1211 int pos = 0;
1212 while ((pos=text.indexOf('\t', oldPos))>-1) {
1213 //repTabsSB.append(text, oldPos, pos); // Added in Java 1.5
1214 if (pos>oldPos) {
1215 repTabsSB.append(array, oldPos, pos-oldPos);
1216 }
1217 repTabsSB.append(tabText);
1218 oldPos = pos + 1;
1219 }
1220 if (oldPos<array.length) {
1221 repTabsSB.append(array, oldPos, array.length-oldPos);
1222 }
1223
1224 return repTabsSB.toString();
1225
1226 }
1227
1228
1229
1230 /**
1231 * Sets the properties of one of the actions this text area owns.
1232 *
1233 * @param action The action to modify; for example, {@link #CUT_ACTION}.
1234 * @param name The new name for the action.
1235 * @param mnemonic The new mnemonic for the action.
1236 * @param accelerator The new accelerator key for the action.
1237 */
1238 public static void setActionProperties(int action, String name,
1239 char mnemonic, KeyStroke accelerator) {
1240 setActionProperties(action, name, new Integer(mnemonic), accelerator);
1241 }
1242
1243
1244 /**
1245 * Sets the properties of one of the actions this text area owns.
1246 *
1247 * @param action The action to modify; for example, {@link #CUT_ACTION}.
1248 * @param name The new name for the action.
1249 * @param mnemonic The new mnemonic for the action.
1250 * @param accelerator The new accelerator key for the action.
1251 */
1252 public static void setActionProperties(int action, String name,
1253 Integer mnemonic, KeyStroke accelerator) {
1254
1255 Action tempAction = null;
1256
1257 switch (action) {
1258 case CUT_ACTION:
1259 tempAction = cutAction;
1260 break;
1261 case COPY_ACTION:
1262 tempAction = copyAction;
1263 break;
1264 case PASTE_ACTION:
1265 tempAction = pasteAction;
1266 break;
1267 case DELETE_ACTION:
1268 tempAction = deleteAction;
1269 break;
1270 case SELECT_ALL_ACTION:
1271 tempAction = selectAllAction;
1272 break;
1273 case UNDO_ACTION:
1274 case REDO_ACTION:
1275 default:
1276 return;
1277 }
1278
1279 tempAction.putValue(Action.NAME, name);
1280 tempAction.putValue(Action.SHORT_DESCRIPTION, name);
1281 tempAction.putValue(Action.ACCELERATOR_KEY, accelerator);
1282 tempAction.putValue(Action.MNEMONIC_KEY, mnemonic);
1283
1284 }
1285
1286
1287 /**
1288 * This method is overridden to make sure that instances of
1289 * <code>RTextArea</code> only use {@link ConfigurableCaret}s.
1290 * To set the style of caret (vertical line, block, etc.) used for
1291 * insert or overwrite mode, use {@link #setCaretStyle(int, int)}.
1292 *
1293 * @param caret The caret to use. If this is not an instance of
1294 * <code>ConfigurableCaret</code>, an exception is thrown.
1295 * @throws IllegalArgumentException If the specified caret is not an
1296 * <code>ConfigurableCaret</code>.
1297 * @see #setCaretStyle(int, int)
1298 */
1299 public void setCaret(Caret caret) {
1300 if (!(caret instanceof ConfigurableCaret)) {
1301 throw new IllegalArgumentException(
1302 "RTextArea needs ConfigurableCaret");
1303 }
1304 super.setCaret(caret);
1305 if (carets!=null) { // Called by setUI() before carets is initialized
1306 ((ConfigurableCaret)caret).setStyle(carets[getTextMode()]);
1307 }
1308 }
1309
1310
1311 /**
1312 * Sets the style of caret used when in insert or overwrite mode.
1313 *
1314 * @param mode Either {@link #INSERT_MODE} or {@link #OVERWRITE_MODE}.
1315 * @param style The style for the caret (such as
1316 * {@link ConfigurableCaret#VERTICAL_LINE_STYLE}).
1317 * @see org.fife.ui.rtextarea.ConfigurableCaret
1318 */
1319 public void setCaretStyle(int mode, int style) {
1320 style = (style>=ConfigurableCaret.MIN_STYLE &&
1321 style<=ConfigurableCaret.MAX_STYLE ?
1322 style : ConfigurableCaret.THICK_VERTICAL_LINE_STYLE);
1323 carets[mode] = style;
1324 if (mode==getTextMode()) {
1325 // Will repaint the caret if necessary.
1326 ((ConfigurableCaret)getCaret()).setStyle(style);
1327 }
1328 }
1329
1330
1331 /**
1332 * Sets the document used by this text area.
1333 *
1334 * @param document The new document to use.
1335 * @throws IllegalArgumentException If the document is not an instance of
1336 * {@link AbstractDocument}.
1337 */
1338 public void setDocument(Document document) {
1339 if (!(document instanceof AbstractDocument)) {
1340 throw new IllegalArgumentException("RTextArea requires " +
1341 "instances of AbstractDocument for its document");
1342 }
1343 if (undoManager!=null) { // First time through, undoManager==null
1344 Document old = getDocument();
1345 if (old!=null) {
1346 old.removeUndoableEditListener(undoManager);
1347 }
1348 }
1349 super.setDocument(document);
1350 if (undoManager!=null) {
1351 document.addUndoableEditListener(undoManager);
1352 discardAllEdits();
1353 }
1354 }
1355
1356
1357 /**
1358 * Sets the path in which to find images to associate with the editor's
1359 * actions. The path MUST contain the following images (with the
1360 * appropriate extension as defined by the icon group):<br>
1361 * <ul>
1362 * <li>cut</li>
1363 * <li>copy</li>
1364 * <li>paste</li>
1365 * <li>delete</li>
1366 * <li>undo</li>
1367 * <li>redo</li>
1368 * <li>selectall</li>
1369 * </ul>
1370 * If any of the above images don't exist, the corresponding action will
1371 * not have an icon.
1372 *
1373 * @param group The icon group to load.
1374 * @see #getIconGroup()
1375 */
1376 public static synchronized void setIconGroup(IconGroup group) {
1377 Icon icon = group.getIcon("cut");
1378 cutAction.putValue(Action.SMALL_ICON, icon);
1379 icon = group.getIcon("copy");
1380 copyAction.putValue(Action.SMALL_ICON, icon);
1381 icon = group.getIcon("paste");
1382 pasteAction.putValue(Action.SMALL_ICON, icon);
1383 icon = group.getIcon("delete");
1384 deleteAction.putValue(Action.SMALL_ICON, icon);
1385 icon = group.getIcon("undo");
1386 undoAction.putValue(Action.SMALL_ICON, icon);
1387 icon = group.getIcon("redo");
1388 redoAction.putValue(Action.SMALL_ICON, icon);
1389 icon = group.getIcon("selectall");
1390 selectAllAction.putValue(Action.SMALL_ICON, icon);
1391 iconGroup = group;
1392 }
1393
1394
1395 /**
1396 * Sets the color used for "mark all." This fires a property change of
1397 * type {@link #MARK_ALL_COLOR_PROPERTY}.
1398 *
1399 * @param color The color to use for "mark all."
1400 * @see #getMarkAllHighlightColor()
1401 */
1402 public void setMarkAllHighlightColor(Color color) {
1403 Color old = (Color)markAllHighlightPainter.getPaint();
1404 if (old!=null && !old.equals(color)) {
1405 markAllHighlightPainter.setPaint(color);
1406 if (markedWord!=null)
1407 repaint(); // Repaint if words are highlighted.
1408 firePropertyChange(MARK_ALL_COLOR_PROPERTY, old, color);
1409 }
1410 }
1411
1412
1413 /**
1414 * Sets the popup menu used by this text area.<p>
1415 *
1416 * If you set the popup menu with this method, you'll want to consider also
1417 * overriding {@link #configurePopupMenu(JPopupMenu)}, especially if you
1418 * removed any of the default menu items.
1419 *
1420 * @param popupMenu The popup menu. If this is <code>null</code>, no
1421 * popup menu will be displayed.
1422 * @see #getPopupMenu()
1423 * @see #configurePopupMenu(JPopupMenu)
1424 */
1425 public void setPopupMenu(JPopupMenu popupMenu) {
1426 this.popupMenu = popupMenu;
1427 popupMenuCreated = true;
1428 }
1429
1430
1431 /**
1432 * {@inheritDoc}
1433 */
1434 public void setRoundedSelectionEdges(boolean rounded) {
1435 if (getRoundedSelectionEdges()!=rounded) {
1436 markAllHighlightPainter.setRoundedEdges(rounded);
1437 super.setRoundedSelectionEdges(rounded); // Fires event.
1438 }
1439 }
1440
1441
1442 /**
1443 * Sets the text last selected/Ctrl+K'd in an <code>RTextArea</code>.
1444 * This text will be searched for in subsequent Ctrl+K/Ctrl+Shift+K
1445 * actions (Cmd+K on OS X).<p>
1446 *
1447 * Since the selected occurrence actions are built into RTextArea,
1448 * applications usually do not have to call this method directly, but can
1449 * choose to do so if they wish (for example, if they wish to set this
1450 * value when the user does a search via a Find dialog).
1451 *
1452 * @param text The selected text.
1453 * @see #getSelectedOccurrenceText()
1454 */
1455 public static void setSelectedOccurrenceText(String text) {
1456 selectedOccurrenceText = text;
1457 }
1458
1459
1460 /**
1461 * Sets the text mode for this editor pane.
1462 *
1463 * @param mode Either {@link #INSERT_MODE} or {@link #OVERWRITE_MODE}.
1464 */
1465 public void setTextMode(int mode) {
1466
1467 if (mode!=INSERT_MODE && mode!=OVERWRITE_MODE)
1468 mode = INSERT_MODE;
1469
1470 if (textMode != mode) {
1471 ConfigurableCaret cc = (ConfigurableCaret)getCaret();
1472 cc.setStyle(carets[mode]);
1473 textMode = mode;
1474 }
1475
1476 }
1477
1478
1479 /**
1480 * Sets the tool tip supplier.
1481 *
1482 * @param supplier The new tool tip supplier, or <code>null</code> if
1483 * there is to be no supplier.
1484 * @see #getToolTipSupplier()
1485 */
1486 public void setToolTipSupplier(ToolTipSupplier supplier) {
1487 this.toolTipSupplier = supplier;
1488 }
1489
1490
1491 /**
1492 * Sets the UI used by this text area. This is overridden so only the
1493 * right-click popup menu's UI is updated. The look and feel of an
1494 * <code>RTextArea</code> is independent of the Java Look and Feel, and so
1495 * this method does not change the text area itself. Subclasses (such as
1496 * <code>RSyntaxTextArea</code> can call <code>setRTextAreaUI</code> if
1497 * they wish to install a new UI.
1498 *
1499 * @param ui This parameter is ignored.
1500 */
1501 public final void setUI(TextUI ui) {
1502
1503 // Update the popup menu's ui.
1504 if (popupMenu!=null) {
1505 SwingUtilities.updateComponentTreeUI(popupMenu);
1506 }
1507
1508 // Set things like selection color, selected text color, etc. to
1509 // laf defaults (if values are null or UIResource instances).
1510 RTextAreaUI rtaui = (RTextAreaUI)getUI();
1511 if (rtaui!=null) {
1512 rtaui.installDefaults();
1513 }
1514
1515 }
1516
1517
1518 /**
1519 * Attempt to undo an "action" done in this text area.
1520 *
1521 * @see #redoLastAction()
1522 */
1523 public void undoLastAction() {
1524 // NOTE: that the try/catch block shouldn't be necessary...
1525 try {
1526 if (undoManager.canUndo())
1527 undoManager.undo();
1528 }
1529 catch (CannotUndoException cre) {
1530 cre.printStackTrace();
1531 }
1532 }
1533
1534 /**
1535 * Serializes this text area.
1536 *
1537 * @param s The stream to write to.
1538 * @throws IOException If an IO error occurs.
1539 */
1540 private void writeObject(ObjectOutputStream s) throws IOException {
1541
1542 // UndoManagers cannot be serialized without Exceptions. See
1543 // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4275892
1544 getDocument().removeUndoableEditListener(undoManager);
1545 s.defaultWriteObject();
1546 getDocument().addUndoableEditListener(undoManager);
1547
1548 }
1549
1550
1551 /**
1552 * Modified from <code>MutableCaretEvent</code> in
1553 * <code>JTextComponent</code> so that mouse events get fired when the user
1554 * is selecting text with the mouse as well. This class also displays the
1555 * popup menu when the user right-clicks in the text area.
1556 */
1557 protected class RTextAreaMutableCaretEvent extends RTAMouseListener {
1558
1559 protected RTextAreaMutableCaretEvent(RTextArea textArea) {
1560 super(textArea);
1561 }
1562
1563 public void focusGained(FocusEvent e) {
1564 Caret c = getCaret();
1565 boolean enabled = c.getDot()!=c.getMark();
1566 cutAction.setEnabled(enabled);
1567 copyAction.setEnabled(enabled);
1568 undoManager.updateActions(); // To reflect this text area.
1569 }
1570
1571 public void focusLost(FocusEvent e) {
1572 }
1573
1574 public void mouseDragged(MouseEvent e) {
1575 if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
1576 Caret caret = getCaret();
1577 dot = caret.getDot();
1578 mark = caret.getMark();
1579 fireCaretUpdate(this);
1580 }
1581 }
1582
1583 public void mousePressed(MouseEvent e) {
1584 // WORKAROUND: Since JTextComponent only updates the caret
1585 // location on mouse clicked and released, we'll do it on dragged
1586 // events when the left mouse button is clicked.
1587 if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
1588 Caret caret = getCaret();
1589 dot = caret.getDot();
1590 mark = caret.getMark();
1591 fireCaretUpdate(this);
1592 }
1593 }
1594
1595 public void mouseReleased(MouseEvent e) {
1596 if ((e.getModifiers()&MouseEvent.BUTTON3_MASK)!=0)
1597 showPopup(e);
1598 }
1599
1600 /**
1601 * Shows a popup menu with cut, copy, paste, etc. options if the
1602 * user clicked the right button.
1603 *
1604 * @param e The mouse event that caused this method to be called.
1605 */
1606 private void showPopup(MouseEvent e) {
1607 JPopupMenu popupMenu = getPopupMenu();
1608 if (popupMenu!=null) {
1609 configurePopupMenu(popupMenu);
1610 popupMenu.show(e.getComponent(), e.getX(), e.getY());
1611 }
1612 }
1613
1614 }
1615
1616
1617}
Note: See TracBrowser for help on using the repository browser.