Package org.fife.ui.rtextarea

Source Code of org.fife.ui.rtextarea.RTextArea$RTextAreaMutableCaretEvent

/*
* 11/14/2003
*
* RTextArea.java - An extension of JTextArea that adds many features.
*
* This library is distributed under a modified BSD license.  See the included
* RSyntaxTextArea.License.txt file for details.
*/
package org.fife.ui.rtextarea;

import java.awt.Color;
import java.awt.ComponentOrientation;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.FocusEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.print.PageFormat;
import java.awt.print.Printable;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.util.List;
import java.util.Locale;
import java.util.ResourceBundle;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.InputMap;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.CaretEvent;
import javax.swing.plaf.TextUI;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BadLocationException;
import javax.swing.text.Caret;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.Segment;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;

import org.fife.print.RPrintUtilities;
import org.fife.ui.rsyntaxtextarea.DocumentRange;
import org.fife.ui.rtextarea.Macro.MacroRecord;


/**
* An extension of <code>JTextArea</code> that adds the following features:
* <ul>
*    <li>Insert/Overwrite modes (can be toggled via the Insert key)
*    <li>A right-click popup menu with standard editing options
*    <li>Macro support
*    <li>"Mark all" functionality.
*    <li>A way to change the background to an image (gif/png/jpg)
*    <li>Highlight the current line (can be toggled)
*    <li>An easy way to print its text (implements Printable)
*    <li>Hard/soft (emulated with spaces) tabs
*    <li>Fixes a bug with setTabSize
*    <li>Other handy new methods
* </ul>
* NOTE:  If the background for an <code>RTextArea</code> is set to a color,
* its opaque property is set to <code>true</code> for performance reasons.  If
* the background is set to an image, then the opaque property is set to
* <code>false</code>.  This slows things down a little, but if it didn't happen
* then we would see garbage on-screen when the user scrolled through a document
* using the arrow keys (not the page-up/down keys though).  You should never
* have to set the opaque property yourself; it is always done for you.
*
* @author Robert Futrell
* @version 1.0
*/
public class RTextArea extends RTextAreaBase implements Printable {

  /**
   * Constant representing insert mode.
   *
   * @see #setCaretStyle(int, CaretStyle)
   */
  public static final int INSERT_MODE        = 0;

  /**
   * Constant representing overwrite mode.
   *
   * @see #setCaretStyle(int, CaretStyle)
   */
  public static final int OVERWRITE_MODE        = 1;

  /**
   * The property fired when the "mark all" color changes.
   */
  public static final String MARK_ALL_COLOR_PROPERTY  = "RTA.markAllColor";

  /**
   * The property fired when what ranges are labeled "mark all" changes.
   */
  public static final String MARK_ALL_OCCURRENCES_CHANGED_PROPERTY =
      "RTA.markAllOccurrencesChanged";

  /*
   * Constants for all actions.
   */
  private static final int MIN_ACTION_CONSTANT  = 0;
  public static final int COPY_ACTION        = 0;
  public static final int CUT_ACTION        = 1;
  public static final int DELETE_ACTION      = 2;
  public static final int PASTE_ACTION      = 3;
  public static final int REDO_ACTION        = 4;
  public static final int SELECT_ALL_ACTION    = 5;
  public static final int UNDO_ACTION        = 6;
  private static final int MAX_ACTION_CONSTANT  = 6;

  private static final Color DEFAULT_MARK_ALL_COLOR = new Color(0xffc800);

  /**
   * The current text mode ({@link #INSERT_MODE} or {@link #OVERWRITE_MODE}).
   */
  private int textMode;

  // All macros are shared across all RTextAreas.
  private static boolean recordingMacro;    // Whether we're recording a macro.
  private static Macro currentMacro;

  /**
   * This text area's popup menu.
   */
  private JPopupMenu popupMenu;

  private JMenuItem undoMenuItem;
  private JMenuItem redoMenuItem;
  private JMenuItem cutMenuItem;
  private JMenuItem pasteMenuItem;
  private JMenuItem deleteMenuItem;

  /**
   * Whether the popup menu has been created.
   */
  private boolean popupMenuCreated;

  /**
   * The text last searched for via Ctrl+K or Ctrl+Shift+K.
   */
  private static String selectedOccurrenceText;

  /**
   * Can return tool tips for this text area.  Subclasses can install a
   * supplier as a means of adding custom tool tips without subclassing
   * <tt>RTextArea</tt>{@link #getToolTipText()} checks this supplier
   * before calling the super class's version.
   */
  private ToolTipSupplier toolTipSupplier;

  private static RecordableTextAction cutAction;
  private static RecordableTextAction copyAction;
  private static RecordableTextAction pasteAction;
  private static RecordableTextAction deleteAction;
  private static RecordableTextAction undoAction;
  private static RecordableTextAction redoAction;
  private static RecordableTextAction selectAllAction;

  private static IconGroup iconGroup;    // Info on icons for actions.

  private transient RUndoManager undoManager;

  private transient LineHighlightManager lineHighlightManager;

  private SmartHighlightPainter markAllHighlightPainter;

  private CaretStyle[] carets;  // Index 0=>insert caret, 1=>overwrite.

  private static final String MSG  = "org.fife.ui.rtextarea.RTextArea";


  /**
   * Constructor.
   */
  public RTextArea() {
  }


  /**
   * Constructor.
   *
   * @param doc The document for the editor.
   */
  public RTextArea(AbstractDocument doc) {
    super(doc);
  }


  /**
   * Constructor.
   *
   * @param text The initial text to display.
   */
  public RTextArea(String text) {
    super(text);
  }


  /**
   * Constructor.
   *
   * @param rows The number of rows to display.
   * @param cols The number of columns to display.
   * @throws IllegalArgumentException If either <code>rows</code> or
   *         <code>cols</code> is negative.
   */
  public RTextArea(int rows, int cols) {
    super(rows, cols);
  }


  /**
   * Constructor.
   *
   * @param text The initial text to display.
   * @param rows The number of rows to display.
   * @param cols The number of columns to display.
   * @throws IllegalArgumentException If either <code>rows</code> or
   *         <code>cols</code> is negative.
   */
  public RTextArea(String text, int rows, int cols) {
    super(text, rows, cols);
  }


  /**
   * Constructor.
   *
   * @param doc The document for the editor.
   * @param text The initial text to display.
   * @param rows The number of rows to display.
   * @param cols The number of columns to display.
   * @throws IllegalArgumentException If either <code>rows</code> or
   *         <code>cols</code> is negative.
   */
  public RTextArea(AbstractDocument doc, String text, int rows, int cols) {
    super(doc, text, rows, cols);
  }


  /**
   * Creates a new <code>RTextArea</code>.
   *
   * @param textMode Either <code>INSERT_MODE</code> or
   *        <code>OVERWRITE_MODE</code>.
   */
  public RTextArea(int textMode) {
    setTextMode(textMode);
  }


  /**
   * Adds an action event to the current macro.  This shouldn't be called
   * directly, as it is called by the actions themselves.
   *
   * @param id The ID of the recordable text action.
   * @param actionCommand The "command" of the action event passed to it.
   */
  static synchronized void addToCurrentMacro(String id,
                      String actionCommand) {
    currentMacro.addMacroRecord(new Macro.MacroRecord(id, actionCommand));
  }


  /**
   * Adds a line highlight.
   *
   * @param line The line to highlight.  This is zero-based.
   * @param color The color to highlight the line with.
   * @throws BadLocationException If <code>line</code> is an invalid line
   *         number.
   * @see #removeLineHighlight(Object)
   * @see #removeAllLineHighlights()
   */
  public Object addLineHighlight(int line, Color color)
                    throws BadLocationException {
    if (lineHighlightManager==null) {
      lineHighlightManager = new LineHighlightManager(this);
    }
    return lineHighlightManager.addLineHighlight(line, color);
  }


  /**
   * Begins an "atomic edit."  All text editing operations between this call
   * and the next call to <tt>endAtomicEdit()</tt> will be treated as a
   * single operation by the undo manager.<p>
   *
   * Using this method should be done with great care.  You should probably
   * wrap the call to <tt>endAtomicEdit()</tt> in a <tt>finally</tt> block:
   *
   * <pre>
   * textArea.beginAtomicEdit();
   * try {
   *    // Do editing
   * } finally {
   *    textArea.endAtomicEdit();
   * }
   * </pre>
   *
   * @see #endAtomicEdit()
   */
  public void beginAtomicEdit() {
    undoManager.beginInternalAtomicEdit();
  }


  /**
   * Begins recording a macro.  After this method is called, all input/caret
   * events, etc. are recorded until <code>endMacroRecording</code> is
   * called.  If this method is called but the text component is already
   * recording a macro, nothing happens (but the macro keeps recording).
   *
   * @see #isRecordingMacro()
   * @see #endRecordingMacro()
   */
  public static synchronized void beginRecordingMacro() {
    if (isRecordingMacro()) {
      //System.err.println("Macro already being recorded!");
      return;
    }
    //JOptionPane.showMessageDialog(this, "Now recording a macro");
    if (currentMacro!=null)
      currentMacro = null; // May help gc?
    currentMacro = new Macro();
    recordingMacro = true;
  }


  /**
   * Tells whether an undo is possible
   *
   * @see #canRedo()
   * @see #undoLastAction()
   */
  public boolean canUndo() {
    return undoManager.canUndo();
  }


  /**
   * Tells whether a redo is possible
   *
   * @see #canUndo()
   * @see #redoLastAction()
   */
  public boolean canRedo() {
    return undoManager.canRedo();
  }


  /**
   * Clears any "mark all" highlights, if any.
   *
   * @see #markAll(List)
   * @see #getMarkAllHighlightColor()
   * @see #setMarkAllHighlightColor(Color)
   */
  void clearMarkAllHighlights() {
    ((RTextAreaHighlighter)getHighlighter()).clearMarkAllHighlights();
    //markedWord = null;
    repaint();
  }


  /**
   * Configures the popup menu for this text area.  This method is called
   * right before it is displayed, so a hosting application can do any
   * custom configuration (configuring actions, adding/removing items, etc.).
   * <p>
   *
   * The default implementation does nothing.<p>
   *
   * If you set the popup menu via {@link #setPopupMenu(JPopupMenu)}, you
   * will want to override this method, especially if you removed any of the
   * menu items in the default popup menu.
   *
   * @param popupMenu The popup menu.  This will never be <code>null</code>.
   * @see #createPopupMenu()
   * @see #setPopupMenu(JPopupMenu)
   */
  protected void configurePopupMenu(JPopupMenu popupMenu) {

    boolean canType = isEditable() && isEnabled();

    // Since the user can customize the popup menu, these actions may not
    // have been created.
    if (undoMenuItem!=null) {
      undoMenuItem.setEnabled(undoAction.isEnabled() && canType);
      redoMenuItem.setEnabled(redoAction.isEnabled() && canType);
      cutMenuItem.setEnabled(cutAction.isEnabled() && canType);
      pasteMenuItem.setEnabled(pasteAction.isEnabled() && canType);
      deleteMenuItem.setEnabled(deleteAction.isEnabled() && canType);
    }

  }


  /**
   * Creates the default implementation of the model to be used at
   * construction if one isn't explicitly given. A new instance of RDocument
   * is returned.
   *
   * @return The default document.
   */
  @Override
  protected Document createDefaultModel() {
    return new RDocument();
  }

  /**
   * Returns the caret event/mouse listener for <code>RTextArea</code>s.
   *
   * @return The caret event/mouse listener.
   */
  @Override
  protected RTAMouseListener createMouseListener() {
    return new RTextAreaMutableCaretEvent(this);
  }


  /**
   * Creates the right-click popup menu. Subclasses can override this method
   * to replace or augment the popup menu returned.
   *
   * @return The popup menu.
   * @see #setPopupMenu(JPopupMenu)
   * @see #configurePopupMenu(JPopupMenu)
   * @see #createPopupMenuItem(Action)
   */
  protected JPopupMenu createPopupMenu() {
    JPopupMenu menu = new JPopupMenu();
    menu.add(undoMenuItem = createPopupMenuItem(undoAction));
    menu.add(redoMenuItem = createPopupMenuItem(redoAction));
    menu.addSeparator();
    menu.add(cutMenuItem = createPopupMenuItem(cutAction));
    menu.add(createPopupMenuItem(copyAction));
    menu.add(pasteMenuItem = createPopupMenuItem(pasteAction));
    menu.add(deleteMenuItem = createPopupMenuItem(deleteAction));
    menu.addSeparator();
    menu.add(createPopupMenuItem(selectAllAction));
    return menu;
  }


  /**
   * Creates the actions used in the popup menu and retrievable by
   * {@link #getAction(int)}.
   * TODO: Remove these horrible hacks and move localizing of actions into
   * the editor kits, where it should be!  The context menu should contain
   * actions from the editor kits.
   */
  private static void createPopupMenuActions() {

    // Create actions for right-click popup menu.
    // 1.5.2004/pwy: Replaced the CTRL_MASK with the cross-platform version...
    int mod = Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
    ResourceBundle msg = ResourceBundle.getBundle(MSG);

    cutAction = new RTextAreaEditorKit.CutAction();
    cutAction.setProperties(msg, "Action.Cut");
    cutAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_X, mod));
    copyAction = new RTextAreaEditorKit.CopyAction();
    copyAction.setProperties(msg, "Action.Copy");
    copyAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, mod));
    pasteAction = new RTextAreaEditorKit.PasteAction();
    pasteAction.setProperties(msg, "Action.Paste");
    pasteAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, mod));
    deleteAction = new RTextAreaEditorKit.DeleteNextCharAction();
    deleteAction.setProperties(msg, "Action.Delete");
    deleteAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0));
    undoAction = new RTextAreaEditorKit.UndoAction();
    undoAction.setProperties(msg, "Action.Undo");
    undoAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, mod));
    redoAction = new RTextAreaEditorKit.RedoAction();
    redoAction.setProperties(msg, "Action.Redo");
    redoAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Y, mod));
    selectAllAction = new RTextAreaEditorKit.SelectAllAction();
    selectAllAction.setProperties(msg, "Action.SelectAll");
    selectAllAction.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, mod));

  }


  /**
   * Creates and configures a menu item for used in the popup menu.
   *
   * @param a The action for the menu item.
   * @return The menu item.
   * @see #createPopupMenu()
   */
  protected JMenuItem createPopupMenuItem(Action a) {
    JMenuItem item = new JMenuItem(a) {
      @Override
      public void setToolTipText(String text) {
        // Ignore!  Actions (e.g. undo/redo) set this when changing
        // their text due to changing enabled state.
      }
    };
    item.setAccelerator(null);
    return item;
  }


  /**
   * Returns the a real UI to install on this text area.
   *
   * @return The UI.
   */
  @Override
  protected RTextAreaUI createRTextAreaUI() {
    return new RTextAreaUI(this);
  }


  /**
   * Creates a string of space characters of the specified size.
   *
   * @param size The number of spaces.
   * @return The string of spaces.
   */
  private final String createSpacer(int size) {
    StringBuilder sb = new StringBuilder();
    for (int i=0; i<size; i++) {
      sb.append(' ');
    }
    return sb.toString();
  }


  /**
   * Creates an undo manager for use in this text area.
   *
   * @return The undo manager.
   */
  protected RUndoManager createUndoManager() {
    return new RUndoManager(this);
  }


  /**
   * Removes all undoable edits from this document's undo manager.  This
   * method also makes the undo/redo actions disabled.
   */
  /*
   * NOTE:  For some reason, it appears I have to create an entirely new
   *        <code>undoManager</code> for undo/redo to continue functioning
   *        properly; if I don't, it only ever lets you do one undo.  Not
   *        too sure why this is...
   */
  public void discardAllEdits() {
    undoManager.discardAllEdits();
    getDocument().removeUndoableEditListener(undoManager);
    undoManager = createUndoManager();
    getDocument().addUndoableEditListener(undoManager);
    undoManager.updateActions();
  }


  /**
   * Completes an "atomic" edit.
   *
   * @see #beginAtomicEdit()
   */
  public void endAtomicEdit() {
    undoManager.endInternalAtomicEdit();
  }


  /**
   * Ends recording a macro.  If this method is called but the text component
   * is not recording a macro, nothing happens.
   *
   * @see #isRecordingMacro()
   * @see #beginRecordingMacro()
   */
  /*
   * FIXME:  This should throw an exception if we're not recording a macro.
   */
  public static synchronized void endRecordingMacro() {
    if (!isRecordingMacro()) {
      //System.err.println("Not recording a macro!");
      return;
    }
    recordingMacro = false;
  }


  /**
   * Notifies all listeners that a caret change has occurred.
   *
   * @param e The caret event.
   */
  @Override
  protected void fireCaretUpdate(CaretEvent e) {

    // Decide whether we need to repaint the current line background.
    possiblyUpdateCurrentLineHighlightLocation();

    // Now, if there is a highlighted region of text, allow them to cut
    // and copy.
    if (e!=null && e.getDot()!=e.getMark()) {// && !cutAction.isEnabled()) {
      cutAction.setEnabled(true);
      copyAction.setEnabled(true);
    }

    // Otherwise, if there is no highlighted region, don't let them cut
    // or copy.  The condition here should speed things up, because this
    // way, we will only enable the actions the first time the selection
    // becomes nothing.
    else if (cutAction.isEnabled()) {
      cutAction.setEnabled(false);
      copyAction.setEnabled(false);
    }

    super.fireCaretUpdate(e);

  }


  /**
   * Removes the "Ctrl+H <=> Backspace" behavior that Java shows, for some
   * odd reason...
   */
  private void fixCtrlH() {
    InputMap inputMap = getInputMap();
    KeyStroke char010 = KeyStroke.getKeyStroke("typed \010");
    InputMap parent = inputMap;
    while (parent != null) {
      parent.remove(char010);
      parent = parent.getParent();
    }
    KeyStroke backspace = KeyStroke.getKeyStroke("BACK_SPACE");
    inputMap.put(backspace, DefaultEditorKit.deletePrevCharAction);
  }


  /**
   * Provides a way to gain access to the editor actions on the right-click
   * popup menu.  This way you can make toolbar/menu bar items use the actual
   * actions used by all <code>RTextArea</code>s, so that icons stay
   * synchronized and you don't have to worry about enabling/disabling them
   * yourself.<p>
   * Keep in mind that these actions are shared across all instances of
   * <code>RTextArea</code>, so a change to any action returned by this
   * method is global across all <code>RTextArea</code> editors in your
   * application.
   *
   * @param action The action to retrieve, such as {@link #CUT_ACTION}.
   *        If the action name is invalid, <code>null</code> is returned.
   * @return The action, or <code>null</code> if an invalid action is
   *         requested.
   */
  public static RecordableTextAction getAction(int action) {
    if (action<MIN_ACTION_CONSTANT || action>MAX_ACTION_CONSTANT)
      return null;
    switch (action) {
      case COPY_ACTION:
        return copyAction;
      case CUT_ACTION:
        return cutAction;
      case DELETE_ACTION:
        return deleteAction;
      case PASTE_ACTION:
        return pasteAction;
      case REDO_ACTION:
        return redoAction;
      case SELECT_ALL_ACTION:
        return selectAllAction;
      case UNDO_ACTION:
        return undoAction;
    }
    return null;
  }


  /**
   * Returns the macro currently stored in this <code>RTextArea</code>.
   * Since macros are shared, all <code>RTextArea</code>s in the currently-
   * running application are using this macro.
   *
   * @return The current macro, or <code>null</code> if no macro has been
   *         recorded/loaded.
   * @see #loadMacro(Macro)
   */
  public static synchronized Macro getCurrentMacro() {
    return currentMacro;
  }


  /**
   * Returns the default color used for "mark all."
   *
   * @return The color.
   * @see #getMarkAllHighlightColor()
   * @see #setMarkAllHighlightColor(Color)
   */
  public static final Color getDefaultMarkAllHighlightColor() {
    return DEFAULT_MARK_ALL_COLOR;
  }


  /**
   * Returns the icon group being used for the actions of this text area.
   *
   * @return The icon group.
   * @see #setIconGroup(IconGroup)
   */
  public static IconGroup getIconGroup() {
    return iconGroup;
  }


  /**
   * Returns the line highlight manager.
   *
   * @return The line highlight manager.  This may be <code>null</code>.
   */
  LineHighlightManager getLineHighlightManager() {
    return lineHighlightManager;
  }


  /**
   * Returns the color used in "mark all."
   *
   * @return The color.
   * @see #setMarkAllHighlightColor(Color)
   */
  public Color getMarkAllHighlightColor() {
    return (Color)markAllHighlightPainter.getPaint();
  }


  /**
   * Returns the maximum ascent of all fonts used in this text area.  In
   * the case of a standard <code>RTextArea</code>, this is simply the
   * ascent of the current font.<p>
   *
   * This value could be useful, for example, to implement a line-numbering
   * scheme.
   *
   * @return The ascent of the current font.
   */
  public int getMaxAscent() {
    return getFontMetrics(getFont()).getAscent();
  }


  /**
   * Returns the popup menu for this component, lazily creating it if
   * necessary.
   *
   * @return The popup menu.
   * @see #createPopupMenu()
   * @see #setPopupMenu(JPopupMenu)
   */
  public JPopupMenu getPopupMenu() {
    if (!popupMenuCreated) {
      popupMenu = createPopupMenu();
      if (popupMenu!=null) {
        ComponentOrientation orientation = ComponentOrientation.
                    getOrientation(Locale.getDefault());
        popupMenu.applyComponentOrientation(orientation);
      }
      popupMenuCreated = true;
    }
    return popupMenu;
  }


  /**
   * Returns the text last selected and used in a Ctrl+K operation.
   *
   * @return The text, or <code>null</code> if none.
   * @see #setSelectedOccurrenceText(String)
   */
  public static String getSelectedOccurrenceText() {
    return selectedOccurrenceText;
  }


  /**
   * Returns the text mode this editor pane is currently in.
   *
   * @return Either {@link #INSERT_MODE} or {@link #OVERWRITE_MODE}.
   * @see #setTextMode(int)
   */
  public final int getTextMode() {
    return textMode;
  }


  /**
   * Returns the tool tip supplier.
   *
   * @return The tool tip supplier, or <code>null</code> if one isn't
   *         installed.
   * @see #setToolTipSupplier(ToolTipSupplier)
   */
  public ToolTipSupplier getToolTipSupplier() {
    return toolTipSupplier;
  }


  /**
   * Returns the tooltip to display for a mouse event at the given
   * location.  This method is overridden to check for a
   * {@link ToolTipSupplier}; if there is one installed, it is queried for
   * tool tip text before using the super class's implementation of this
   * method.
   *
   * @param e The mouse event.
   * @return The tool tip text, or <code>null</code> if none.
   * @see #getToolTipSupplier()
   * @see #setToolTipSupplier(ToolTipSupplier)
   */
  @Override
  public String getToolTipText(MouseEvent e) {
    String tip = null;
    if (getToolTipSupplier()!=null) {
      tip = getToolTipSupplier().getToolTipText(this, e);
    }
    return tip!=null ? tip : super.getToolTipText();
  }


  /**
   * Does the actual dirty-work of replacing the selected text in this
   * text area (i.e., in its document).  This method provides a hook for
   * subclasses to handle this in a different way.
   *
   * @param content The content to add.
   */
  protected void handleReplaceSelection(String content) {
    // Call into super to handle composed text.
    super.replaceSelection(content);
  }


  /**
   * {@inheritDoc}
   */
  @Override
  protected void init() {

    super.init();

    // NOTE: Our actions are created here instead of in a static block
    // so they are only created when the first RTextArea is instantiated,
    // not before.  There have been reports of users calling static getters
    // (e.g. RSyntaxTextArea.getDefaultBracketMatchBGColor()) which would
    // cause these actions to be created and (possibly) incorrectly
    // localized, if they were in a static block.
    if (cutAction==null) {
      createPopupMenuActions();
    }

    // Install the undo manager.
    undoManager = createUndoManager();
    getDocument().addUndoableEditListener(undoManager);

    // Set the defaults for various stuff.
    Color markAllHighlightColor = getDefaultMarkAllHighlightColor();
    markAllHighlightPainter = new SmartHighlightPainter(
                    markAllHighlightColor);
    setMarkAllHighlightColor(markAllHighlightColor);
    carets = new CaretStyle[2];
    setCaretStyle(INSERT_MODE, CaretStyle.THICK_VERTICAL_LINE_STYLE);
    setCaretStyle(OVERWRITE_MODE, CaretStyle.BLOCK_STYLE);
    setDragEnabled(true);      // Enable drag-and-drop.

    setTextMode(INSERT_MODE); // Carets array must be created first!

    // Fix the odd "Ctrl+H <=> Backspace" Java behavior.
    fixCtrlH();

  }


  /**
   * Returns whether or not a macro is being recorded.
   *
   * @return Whether or not a macro is being recorded.
   * @see #beginRecordingMacro()
   * @see #endRecordingMacro()
   */
  public static synchronized boolean isRecordingMacro() {
    return recordingMacro;
  }


  /**
   * Loads a macro to be used by all <code>RTextArea</code>s in the current
   * application.
   *
   * @param macro The macro to load.
   * @see #getCurrentMacro()
   */
  public static synchronized void loadMacro(Macro macro) {
    currentMacro = macro;
  }


  /**
   * Marks all ranges specified with the "mark all" highlighter.  Typically,
   * this method is called indirectly from {@link SearchEngine} when doing
   * a fine or replace operation.<p>
   *
   * This method fires a property change event of type
   * {@link #MARK_ALL_OCCURRENCES_CHANGED_PROPERTY}.
   *
   * @param ranges The ranges to mark.  This should not be <code>null</code>.
   * @see SearchEngine
   * @see SearchContext#setMarkAll(boolean)
   * @see #clearMarkAllHighlights()
   * @see #getMarkAllHighlightColor()
   * @see #setMarkAllHighlightColor(Color)
   */
  void markAll(List<DocumentRange> ranges) {

    RTextAreaHighlighter h = (RTextAreaHighlighter)getHighlighter();
    if (/*toMark!=null && !toMark.equals(markedWord) && */h!=null) {

      //markedWord = toMark;
      if (ranges!=null) {
        for (DocumentRange range : ranges) {
          try {
            h.addMarkAllHighlight(
                range.getStartOffset(), range.getEndOffset(),
                markAllHighlightPainter);
          } catch (BadLocationException ble) {
            ble.printStackTrace();
          }
        }
      }

      repaint();
      firePropertyChange(MARK_ALL_OCCURRENCES_CHANGED_PROPERTY,
          null, ranges);

    }

  }


  /**
   * {@inheritDoc}
   */
  @Override
  public void paste() {
    // Treat paste operations as atomic, otherwise the removal and
    // insertion are treated as two separate undo-able operations.
    beginAtomicEdit();
    try {
      super.paste();
    } finally {
      endAtomicEdit();
    }
  }


  /**
   * "Plays back" the last recorded macro in this text area.
   */
  public synchronized void playbackLastMacro() {
    if (currentMacro!=null) {
      List<MacroRecord> macroRecords = currentMacro.getMacroRecords();
      if (!macroRecords.isEmpty()) {
        Action[] actions = getActions();
        undoManager.beginInternalAtomicEdit();
        try {
          for (MacroRecord record : macroRecords) {
            for (int i=0; i<actions.length; i++) {
              if ((actions[i] instanceof RecordableTextAction) &&
                record.id.equals(
                ((RecordableTextAction)actions[i]).getMacroID())) {
                actions[i].actionPerformed(
                  new ActionEvent(this,
                        ActionEvent.ACTION_PERFORMED,
                        record.actionCommand));
                break;
              }
            }
          }
        } finally {
          undoManager.endInternalAtomicEdit();
        }
      }
    }
  }


  /**
   * Method called when it's time to print this badboy (the old-school,
   * AWT way).
   *
   * @param g The context into which the page is drawn.
   * @param pageFormat The size and orientation of the page being drawn.
   * @param pageIndex The zero based index of the page to be drawn.
   */
  public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
    return RPrintUtilities.printDocumentWordWrap(g, this, getFont(), pageIndex, pageFormat, getTabSize());
  }


  /**
   * We override this method because the super version gives us an entirely
   * new <code>Document</code>, thus requiring us to re-attach our Undo
   * manager.  With this version we just replace the text.
   */
  @Override
  public void read(Reader in, Object desc) throws IOException {

    RTextAreaEditorKit kit = (RTextAreaEditorKit)getUI().getEditorKit(this);
    setText(null);
    Document doc = getDocument();
    if (desc != null)
      doc.putProperty(Document.StreamDescriptionProperty, desc);
    try {
      // NOTE:  Resets the "line separator" property.
      kit.read(in, doc, 0);
    } catch (BadLocationException e) {
      throw new IOException(e.getMessage());
    }

  }


  /**
   * De-serializes a text area.
   *
   * @param s The stream to read from.
   * @throws ClassNotFoundException
   * @throws IOException
   */
  private void readObject(ObjectInputStream s)
            throws ClassNotFoundException, IOException {

    s.defaultReadObject();

    // UndoManagers cannot be serialized without Exceptions.  See
    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4275892
    undoManager = createUndoManager();
    getDocument().addUndoableEditListener(undoManager);

    lineHighlightManager = null; // Keep FindBugs happy.

  }


  /**
   * Attempt to redo the last action.
   *
   * @see #undoLastAction()
   */
  public void redoLastAction() {
    // NOTE:  The try/catch block shouldn't be necessary...
    try {
      if (undoManager.canRedo())
        undoManager.redo();
    } catch (CannotRedoException cre) {
      cre.printStackTrace();
    }
  }


  /**
   * Removes all line highlights.
   *
   * @see #removeLineHighlight(Object)
   */
  public void removeAllLineHighlights() {
    if (lineHighlightManager!=null) {
      lineHighlightManager.removeAllLineHighlights();
    }
  }


  /**
   * Removes a line highlight.
   *
   * @param tag The tag of the line highlight to remove.
   * @see #removeAllLineHighlights()
   * @see #addLineHighlight(int, Color)
   */
  public void removeLineHighlight(Object tag) {
    if (lineHighlightManager!=null) {
      lineHighlightManager.removeLineHighlight(tag);
    }
  }


  /**
   * Replaces text from the indicated start to end position with the
   * new text specified.  Does nothing if the model is null.  Simply
   * does a delete if the new string is null or empty.
   * <p>
   * This method is thread safe, although most Swing methods
   * are not.<p>
   * This method is overridden so that our Undo manager remembers it as a
   * single operation (it has trouble with this, especially for
   * <code>RSyntaxTextArea</code> and the "auto-indent" feature).
   *
   * @param str the text to use as the replacement
   * @param start the start position >= 0
   * @param end the end position >= start
   * @exception IllegalArgumentException  if part of the range is an
   *  invalid position in the model
   * @see #insert(String, int)
   * @see #replaceRange(String, int, int)
   */
  @Override
  public void replaceRange(String str, int start, int end) {
    if (end < start)
      throw new IllegalArgumentException("end before start");
    Document doc = getDocument();
    if (doc != null) {
      try {
        // Without this, in some cases we'll have to do two undos
        // for one logical operation (for example, try editing a
        // Java source file in an RSyntaxTextArea, and moving a line
        // with text already on it down via Enter.  Without this
        // line, doing a single "undo" moves all later text up,
        // but the first line moved down isn't there!  Doing a
        // second undo puts it back.
        undoManager.beginInternalAtomicEdit();
        ((AbstractDocument)doc).replace(start, end - start,
                                                        str, null);
      } catch (BadLocationException e) {
        throw new IllegalArgumentException(e.getMessage());
      } finally {
        undoManager.endInternalAtomicEdit();
      }
    }
    }


  /**
   * This method overrides <code>JTextComponent</code>'s
   * <code>replaceSelection</code>, so that if <code>textMode</code> is
   * {@link #OVERWRITE_MODE}, it actually overwrites.
   *
   * @param text The content to replace the selection with.
   */
  @Override
  public void replaceSelection(String text) {

    // It's legal for null to be used here...
    if (text==null) {
      handleReplaceSelection(text);
      return;
    }

    if (getTabsEmulated()) {
      int firstTab = text.indexOf('\t');
      if (firstTab>-1) {
        int docOffs = getSelectionStart();
        try {
          text = replaceTabsWithSpaces(text, docOffs, firstTab);
        } catch (BadLocationException ble) { // Never happens
          ble.printStackTrace();
        }
      }
    }

    // If the user wants to overwrite text...
    if (textMode==OVERWRITE_MODE && !"\n".equals(text)) {

      Caret caret = getCaret();
      int caretPos = caret.getDot();
      Document doc = getDocument();
      Element map = doc.getDefaultRootElement();
      int curLine = map.getElementIndex(caretPos);
      int lastLine = map.getElementCount() - 1;

      try {

        // If we're not at the end of a line, select the characters
        // that will be overwritten (otherwise JTextArea will simply
        // insert in front of them).
        int curLineEnd = getLineEndOffset(curLine);
        if (caretPos==caret.getMark() && caretPos!=curLineEnd) {
          if (curLine==lastLine)
            caretPos = Math.min(caretPos+text.length(), curLineEnd);
          else
            caretPos = Math.min(caretPos+text.length(), curLineEnd-1);
          caret.moveDot(caretPos);//moveCaretPosition(caretPos);
        }

      } catch (BadLocationException ble) { // Never happens
        UIManager.getLookAndFeel().provideErrorFeedback(this);
        ble.printStackTrace();
      }

    } // End of if (textMode==OVERWRITE_MODE).

    // Now, actually do the inserting/replacing.  Our undoManager will
    // take care of remembering the remove/insert as atomic if we are in
    // overwrite mode.
    handleReplaceSelection(text);

  }


  private static StringBuilder repTabsSB;
  private static Segment repTabsSeg = new Segment();
  /**
   * Replaces all instances of the tab character in <code>text</code> with
   * the number of spaces equivalent to a tab in this text area.<p>
   *
   * This method should only be called from thread-safe methods, such as
   * {@link #replaceSelection(String)}.
   *
   * @param text The <code>java.lang.String</code> in which to replace tabs
   *        with spaces.  This has already been verified to have at least
   *        one tab character in it.
   * @param docOffs The offset in the document at which the text is being
   *        inserted.
   * @param firstTab The offset into <code>text</code> of the first tab.  Assumed
   *        to be &gt;= 0.
   * @return A <code>String</code> just like <code>text</code>, but with
   *         spaces instead of tabs.
   */
  private final String replaceTabsWithSpaces(String text, int docOffs, int firstTab)
      throws BadLocationException {

    int tabSize = getTabSize();

    // Get how many chars into the current line we are
    Document doc = getDocument();
    Element root = doc.getDefaultRootElement();
    int lineIndex = root.getElementIndex(docOffs);
    Element line = root.getElement(lineIndex);
    int lineStart = line.getStartOffset();
    int charCount = docOffs - lineStart;

    // Figure out how many chars into the "current tab" we are
    if (charCount>0) {
      doc.getText(lineStart, charCount, repTabsSeg);
      charCount = 0;
      for (int i=0; i<repTabsSeg.count; i++) {
        char ch = repTabsSeg.array[repTabsSeg.offset + i];
        if (ch=='\t') {
          charCount = 0;
        }
        else {
          charCount = (charCount + 1) % tabSize;
        }
      }
    }

    // Common case: The user's entering a single tab (pressed the tab key).
    if (text.length()==1) {
      return createSpacer(tabSize - charCount);
    }

    // Otherwise, there may be more than one tab.

    if (repTabsSB==null) {
      repTabsSB = new StringBuilder();
    }
    repTabsSB.setLength(0);
    char[] array = text.toCharArray();
    int lastPos = 0;
    int offsInLine = charCount; // Accurate enough for our start
    for (int pos=firstTab; pos<array.length; pos++) {
      char ch = array[pos];
      switch (ch) {
        case '\t':
          if (pos>lastPos) {
            repTabsSB.append(array, lastPos, pos-lastPos);
          }
          int thisTabSize = tabSize - (offsInLine%tabSize);
          repTabsSB.append(createSpacer(thisTabSize));
          lastPos = pos + 1;
          offsInLine = 0;
          break;
        case '\n':
          offsInLine = 0;
          break;
        default:
          offsInLine++;
          break;
      }
    }
    if (lastPos<array.length) {
      repTabsSB.append(array, lastPos, array.length-lastPos);
    }

    return repTabsSB.toString();

  }



  /**
   * Sets the properties of one of the actions this text area owns.
   *
   * @param action The action to modify; for example, {@link #CUT_ACTION}.
   * @param name The new name for the action.
   * @param mnemonic The new mnemonic for the action.
   * @param accelerator The new accelerator key for the action.
   */
  public static void setActionProperties(int action, String name,
              char mnemonic, KeyStroke accelerator) {
    setActionProperties(action, name, Integer.valueOf(mnemonic), accelerator);
  }


  /**
   * Sets the properties of one of the actions this text area owns.
   *
   * @param action The action to modify; for example, {@link #CUT_ACTION}.
   * @param name The new name for the action.
   * @param mnemonic The new mnemonic for the action.
   * @param accelerator The new accelerator key for the action.
   */
  public static void setActionProperties(int action, String name,
              Integer mnemonic, KeyStroke accelerator) {

    Action tempAction = null;

    switch (action) {
      case CUT_ACTION:
        tempAction = cutAction;
        break;
      case COPY_ACTION:
        tempAction = copyAction;
        break;
      case PASTE_ACTION:
        tempAction = pasteAction;
        break;
      case DELETE_ACTION:
        tempAction = deleteAction;
        break;
      case SELECT_ALL_ACTION:
        tempAction = selectAllAction;
        break;
      case UNDO_ACTION:
      case REDO_ACTION:
      default:
        return;
    }

    tempAction.putValue(Action.NAME, name);
    tempAction.putValue(Action.SHORT_DESCRIPTION, name);
    tempAction.putValue(Action.ACCELERATOR_KEY, accelerator);
    tempAction.putValue(Action.MNEMONIC_KEY, mnemonic);

  }


  /**
   * Sets the caret to use in this text area.  It is strongly encouraged to
   * use {@link ConfigurableCaret}s (which is used by default), or a
   * subclass, since they know how to render themselves differently when the
   * user toggles between insert and overwrite modes.
   *
   * @param caret The caret to use.  If this is not an instance of
   *        <code>ConfigurableCaret</code>, an exception is thrown.
   * @throws IllegalArgumentException If the specified caret is not an
   *         <code>ConfigurableCaret</code>.
   * @see #setCaretStyle(int, CaretStyle)
   */
  @Override
  public void setCaret(Caret caret) {
    super.setCaret(caret);
    if (carets!=null && // Called by setUI() before carets is initialized
        caret instanceof ConfigurableCaret) {
      ((ConfigurableCaret)caret).setStyle(carets[getTextMode()]);
    }
  }


  /**
   * Sets the style of caret used when in insert or overwrite mode.
   *
   * @param mode Either {@link #INSERT_MODE} or {@link #OVERWRITE_MODE}.
   * @param style The style for the caret.
   * @see ConfigurableCaret
   */
  public void setCaretStyle(int mode, CaretStyle style) {
    if (style==null) {
      style = CaretStyle.THICK_VERTICAL_LINE_STYLE;
    }
    carets[mode] = style;
    if (mode==getTextMode() && getCaret() instanceof ConfigurableCaret) {
      // Will repaint the caret if necessary.
      ((ConfigurableCaret)getCaret()).setStyle(style);
    }
  }


  /**
   * Sets the document used by this text area.
   *
   * @param document The new document to use.
   * @throws IllegalArgumentException If the document is not an instance of
   *         {@link RDocument}.
   */
  @Override
  public void setDocument(Document document) {
    if (!(document instanceof RDocument)) {
      throw new IllegalArgumentException("RTextArea requires " +
        "instances of RDocument for its document");
    }
    if (undoManager!=null) { // First time through, undoManager==null
      Document old = getDocument();
      if (old!=null) {
        old.removeUndoableEditListener(undoManager);
      }
    }
    super.setDocument(document);
    if (undoManager!=null) {
      document.addUndoableEditListener(undoManager);
      discardAllEdits();
    }
  }


  /**
   * Sets the path in which to find images to associate with the editor's
   * actions.  The path MUST contain the following images (with the
   * appropriate extension as defined by the icon group):<br>
   * <ul>
   *   <li>cut</li>
   *   <li>copy</li>
   *   <li>paste</li>
   *   <li>delete</li>
   *   <li>undo</li>
   *   <li>redo</li>
   *   <li>selectall</li>
   * </ul>
   * If any of the above images don't exist, the corresponding action will
   * not have an icon.
   *
   * @param group The icon group to load.
   * @see #getIconGroup()
   */
  public static synchronized void setIconGroup(IconGroup group) {
    Icon icon = group.getIcon("cut");
    cutAction.putValue(Action.SMALL_ICON, icon);
    icon = group.getIcon("copy");
    copyAction.putValue(Action.SMALL_ICON, icon);
    icon = group.getIcon("paste");
    pasteAction.putValue(Action.SMALL_ICON, icon);
    icon = group.getIcon("delete");
    deleteAction.putValue(Action.SMALL_ICON, icon);
    icon = group.getIcon("undo");
    undoAction.putValue(Action.SMALL_ICON, icon);
    icon = group.getIcon("redo");
    redoAction.putValue(Action.SMALL_ICON, icon);
    icon = group.getIcon("selectall");
    selectAllAction.putValue(Action.SMALL_ICON, icon);
    iconGroup = group;
  }


  /**
   * Sets the color used for "mark all."  This fires a property change of
   * type {@link #MARK_ALL_COLOR_PROPERTY}.
   *
   * @param color The color to use for "mark all."
   * @see #getMarkAllHighlightColor()
   */
  public void setMarkAllHighlightColor(Color color) {
    Color old = (Color)markAllHighlightPainter.getPaint();
    if (old!=null && !old.equals(color)) {
      markAllHighlightPainter.setPaint(color);
      RTextAreaHighlighter h = (RTextAreaHighlighter)getHighlighter();
      if (h.getMarkAllHighlightCount()>0) {
        repaint()// Repaint if words are highlighted.
      }
      firePropertyChange(MARK_ALL_COLOR_PROPERTY, old, color);
    }
  }


  /**
   * Sets the popup menu used by this text area.<p>
   *
   * If you set the popup menu with this method, you'll want to consider also
   * overriding {@link #configurePopupMenu(JPopupMenu)}, especially if you
   * removed any of the default menu items.
   *
   * @param popupMenu The popup menu.  If this is <code>null</code>, no
   *        popup menu will be displayed.
   * @see #getPopupMenu()
   * @see #configurePopupMenu(JPopupMenu)
   */
  public void setPopupMenu(JPopupMenu popupMenu) {
    this.popupMenu = popupMenu;
    popupMenuCreated = true;
  }


  /**
   * {@inheritDoc}
   */
  @Override
  public void setRoundedSelectionEdges(boolean rounded) {
    if (getRoundedSelectionEdges()!=rounded) {
      markAllHighlightPainter.setRoundedEdges(rounded);
      super.setRoundedSelectionEdges(rounded); // Fires event.
    }
  }


  /**
   * Sets the text last selected/Ctrl+K'd in an <code>RTextArea</code>.
   * This text will be searched for in subsequent Ctrl+K/Ctrl+Shift+K
   * actions (Cmd+K on OS X).<p>
   *
   * Since the selected occurrence actions are built into RTextArea,
   * applications usually do not have to call this method directly, but can
   * choose to do so if they wish (for example, if they wish to set this
   * value when the user does a search via a Find dialog).
   *
   * @param text The selected text.
   * @see #getSelectedOccurrenceText()
   */
  public static void setSelectedOccurrenceText(String text) {
    selectedOccurrenceText = text;
  }


  /**
   * Sets the text mode for this editor pane.  If the currently installed
   * caret is an instance of {@link ConfigurableCaret}, it will be
   * automatically updated to render itself appropriately for the new text
   * mode.
   *
   * @param mode Either {@link #INSERT_MODE} or {@link #OVERWRITE_MODE}.
   * @see #getTextMode()
   */
  public void setTextMode(int mode) {

    if (mode!=INSERT_MODE && mode!=OVERWRITE_MODE)
      mode = INSERT_MODE;

    if (textMode != mode) {
      Caret caret = getCaret();
      if (caret instanceof ConfigurableCaret) {
        ((ConfigurableCaret)caret).setStyle(carets[mode]);
      }
      textMode = mode;
      // Prevent the caret from blinking while e.g. holding down the
      // Insert key to toggle insert/overwrite modes
      caret.setVisible(false);
      caret.setVisible(true);
    }

  }


  /**
   * Sets the tool tip supplier.
   *
   * @param supplier The new tool tip supplier, or <code>null</code> if
   *        there is to be no supplier.
   * @see #getToolTipSupplier()
   */
  public void setToolTipSupplier(ToolTipSupplier supplier) {
    this.toolTipSupplier = supplier;
  }


  /**
   * Sets the UI used by this text area.  This is overridden so only the
   * right-click popup menu's UI is updated.  The look and feel of an
   * <code>RTextArea</code> is independent of the Java Look and Feel, and so
   * this method does not change the text area itself.  Subclasses (such as
   * <code>RSyntaxTextArea</code> can call <code>setRTextAreaUI</code> if
   * they wish to install a new UI.
   *
   * @param ui This parameter is ignored.
   */
  @Override
  public final void setUI(TextUI ui) {

    // Update the popup menu's ui.
    if (popupMenu!=null) {
      SwingUtilities.updateComponentTreeUI(popupMenu);
    }

    // Set things like selection color, selected text color, etc. to
    // laf defaults (if values are null or UIResource instances).
    RTextAreaUI rtaui = (RTextAreaUI)getUI();
    if (rtaui!=null) {
      rtaui.installDefaults();
    }

  }


  /**
   * Attempt to undo an "action" done in this text area.
   *
   * @see #redoLastAction()
   */
  public void undoLastAction() {
    // NOTE: that the try/catch block shouldn't be necessary...
    try {
      if (undoManager.canUndo())
        undoManager.undo();
    }
    catch (CannotUndoException cre) {
      cre.printStackTrace();
    }
  }

  /**
   * Serializes this text area.
   *
   * @param s The stream to write to.
   * @throws IOException If an IO error occurs.
   */
  private void writeObject(ObjectOutputStream s) throws IOException {

    // UndoManagers cannot be serialized without Exceptions.  See
    // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4275892
    getDocument().removeUndoableEditListener(undoManager);
    s.defaultWriteObject();
    getDocument().addUndoableEditListener(undoManager);

  }


  /**
   * Modified from <code>MutableCaretEvent</code> in
   * <code>JTextComponent</code> so that mouse events get fired when the user
   * is selecting text with the mouse as well.  This class also displays the
   * popup menu when the user right-clicks in the text area.
   */
  protected class RTextAreaMutableCaretEvent extends RTAMouseListener {

    protected RTextAreaMutableCaretEvent(RTextArea textArea) {
      super(textArea);
    }

    @Override
    public void focusGained(FocusEvent e) {
      Caret c = getCaret();
      boolean enabled = c.getDot()!=c.getMark();
      cutAction.setEnabled(enabled);
      copyAction.setEnabled(enabled);
      undoManager.updateActions(); // To reflect this text area.
    }

    @Override
    public void focusLost(FocusEvent e) {
    }

    @Override
    public void mouseDragged(MouseEvent e) {
      // WORKAROUND:  Since JTextComponent only updates the caret
      // location on mouse clicked and released, we'll do it on dragged
      // events when the left mouse button is clicked.
      if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
        Caret caret = getCaret();
        dot = caret.getDot();
        mark = caret.getMark();
        fireCaretUpdate(this);
      }
    }

    @Override
    public void mousePressed(MouseEvent e) {
      if (e.isPopupTrigger()) { // OS X popup triggers are on pressed
        showPopup(e);
      }
      else if ((e.getModifiers() & MouseEvent.BUTTON1_MASK) != 0) {
        Caret caret = getCaret();
        dot = caret.getDot();
        mark = caret.getMark();
        fireCaretUpdate(this);
      }
    }

    @Override
    public void mouseReleased(MouseEvent e) {
      if (e.isPopupTrigger()) {
        showPopup(e);
      }
    }

    /**
     * Shows a popup menu with cut, copy, paste, etc. options if the
     * user clicked the right button.
     *
     * @param e The mouse event that caused this method to be called.
     */
    private void showPopup(MouseEvent e) {
      JPopupMenu popupMenu = getPopupMenu();
      if (popupMenu!=null) {
        configurePopupMenu(popupMenu);
        popupMenu.show(e.getComponent(), e.getX(), e.getY());
        e.consume();
      }
    }

  }


}
TOP

Related Classes of org.fife.ui.rtextarea.RTextArea$RTextAreaMutableCaretEvent

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.