/*
 * jNPad v0.3 - jNPad's an Simple Text Editor written in Java
 *
 * Copyright (C) 2014-2017  rgs
 *
 * Require JDK 1.6 (or later)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 *
 * Info, Questions, Suggestions & Bugs Report to rgsevero@gmail.com
 */

package jnpad.text;

import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.Action;
import javax.swing.KeyStroke;
import javax.swing.UIManager;
import javax.swing.text.DefaultEditorKit;
import javax.swing.text.Document;
import javax.swing.text.Element;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;
import javax.swing.text.Segment;
import javax.swing.undo.CannotRedoException;

import jnpad.JNPadFrame;
import jnpad.config.Accelerators;
import jnpad.config.Config;
import jnpad.config.Updatable;
import jnpad.search.SearchContext;
import jnpad.util.Utilities;

/**
 * The Class JNPadTextActions.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public final class JNPadTextActions {
  /** The Constant ACTION_NAME_UNDO. */
  public static final String      ACTION_NAME_UNDO                      = "jnpad-text-undo";                                 //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_REDO. */
  public static final String      ACTION_NAME_REDO                      = "jnpad-text-redo";                                 //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_COPY_LINE. */
  public static final String      ACTION_NAME_COPY_LINE                 = "jnpad-text-copy-line";                            //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_CUT_LINE. */
  public static final String      ACTION_NAME_CUT_LINE                  = "jnpad-text-cut-line";                             //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETE_PARAGRAPH. */
  public static final String      ACTION_NAME_DELETE_PARAGRAPH          = "jnpad-text-delete-paragraph";                     //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETION_BEGIN_PARAGRAPH. */
  public static final String      ACTION_NAME_DELETION_BEGIN_PARAGRAPH  = "jnpad-text-deletion-begin-paragraph";             //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETION_END_PARAGRAPH. */
  public static final String      ACTION_NAME_DELETION_END_PARAGRAPH    = "jnpad-text-deletion-end-paragraph";               //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETE_LINE. */
  public static final String      ACTION_NAME_DELETE_LINE               = "jnpad-text-delete-line";                          //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETION_BEGIN_LINE. */
  public static final String      ACTION_NAME_DELETION_BEGIN_LINE       = "jnpad-text-deletion-begin-line";                  //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETION_END_LINE. */
  public static final String      ACTION_NAME_DELETION_END_LINE         = "jnpad-text-deletion-end-line";                    //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DELETE_WORD. */
  public static final String      ACTION_NAME_DELETE_WORD               = "jnpad-text-delete-word";                          //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_HIGHLIGHT_ALL_OCCURRENCES. */
  public static final String      ACTION_NAME_HIGHLIGHT_ALL_OCCURRENCES = "jnpad-text-highlight-all-occurrences";            //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_CLEAR_ALL_OCCURRENCES. */
  public static final String      ACTION_NAME_CLEAR_ALL_OCCURRENCES     = "jnpad-text-clear-all-occurrences";                //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_NEXT_OCCURRENCE. */
  public static final String      ACTION_NAME_NEXT_OCCURRENCE           = "jnpad-text-next-occurrence";                      //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_PREVIOUS_OCCURRENCE. */
  public static final String      ACTION_NAME_PREVIOUS_OCCURRENCE       = "jnpad-text-previous-occurrence";                  //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_RIGHT_INDENT. */
  public static final String      ACTION_NAME_RIGHT_INDENT              = "jnpad-text-right-indent";                         //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_LEFT_INDENT. */
  public static final String      ACTION_NAME_LEFT_INDENT               = "jnpad-text-left-indent";                          //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_INCREASE_FONT. */
  public static final String      ACTION_NAME_INCREASE_FONT             = "jnpad-text-increase-font";                        //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_DECREASE_FONT. */
  public static final String      ACTION_NAME_DECREASE_FONT             = "jnpad-text-decrease-font";                        //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_TOGGLE_TEXT_MODE. */
  public static final String      ACTION_NAME_TOGGLE_TEXT_MODE          = "jnpad-text-toggle-text-mode";                     //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_CAPS_LOCK. */
  public static final String      ACTION_NAME_CAPS_LOCK                 = "jnpad-text-caps-lock";                            //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_NUM_LOCK. */
  public static final String      ACTION_NAME_NUM_LOCK                  = "jnpad-text-num-lock";                             //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_COMPLETE_WORD. */
  public static final String      ACTION_NAME_COMPLETE_WORD             = "jnpad-text-complete-word";                        //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_COMPLETE_WORD_ALL. */
  public static final String      ACTION_NAME_COMPLETE_WORD_ALL         = "jnpad-text-complete-word-all";                    //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_TABS_TO_SPACES. */
  public static final String      ACTION_NAME_TABS_TO_SPACES            = "jnpad-text-tabs-to-spaces";                       //$NON-NLS-1$
  
  /** The Constant ACTION_NAME_SPACES_TO_TABS. */
  public static final String      ACTION_NAME_SPACES_TO_TABS            = "jnpad-text-spaces-to-tabs";                       //$NON-NLS-1$

  private HashMap<Object, Action> actions;                                                                                   // the same actions in a hash-map

  private Keymap                  keymap;                                                                                    // the editor's keymap

  Action                          undoAction;
  Action                          redoAction;
  Action                          selectWordAction;
  Action                          deleteParagraphAction;
  Action                          deletionBeginParagraphAction;
  Action                          deletionEndParagraphAction;
  Action                          deleteLineAction;
  Action                          deletionBeginLineAction;
  Action                          deletionEndLineAction;
  Action                          deleteWordAction;
  Action                          copyLineAction;
  Action                          cutLineAction;
  Action                          highlightAllOccurrencesAction;
  Action                          clearAllOccurrencesAction;
  Action                          nextOccurrenceAction;
  Action                          previousOccurrenceAction;
  Action                          rightIndentAction;
  Action                          leftIndentAction;
  Action                          increaseFontAction;
  Action                          decreaseFontAction;
  Action                          insertBreakAction;
  Action                          toggleTextModeAction;
  Action                          capsLockAction;
  Action                          numLockAction;
  Action                          spacesToTabsAction;
  Action                          tabsToSpacesAction;
  Action                          completeWordAction;
  Action                          completeWordAllAction;

  private static JNPadTextActions instance;

  private Action[]                overrideActions;

  /** Logger */
  private final static Logger     LOGGER                                = Logger.getLogger(JNPadTextActions.class.getName());

  /**
   *
   * @param textComponent JTextComponent
   * @return JNPadTextActions
   */
  public static JNPadTextActions getActions(JTextComponent textComponent) {
    if (instance == null) {
      instance = new JNPadTextActions(textComponent);
    }

    if (textComponent != null) {
      textComponent.setKeymap(instance.keymap);
      instance.overrideActions(textComponent);
    }

    return instance;
  }

  /**
   *
   * @param textComponent JTextComponent
   */
  private void overrideActions(JTextComponent textComponent) {
    for (Action action : overrideActions) {
      textComponent.getActionMap().put(action.getValue(Action.NAME), action);
    }
  }

  /**
   * Constructor. Singleton, thus private.
   *
   * @param textComponent JTextComponent
   */
  @SuppressWarnings("nls")
  private JNPadTextActions(JTextComponent textComponent) {
    // install our own keymap, with the existing one as parent
    Keymap origKeymap = textComponent.getKeymap();
    keymap = JTextComponent.addKeymap("jNPad map", origKeymap);

    createActionTable(textComponent);
    
    if(!Accelerators.isUsingCompositeShortcuts()) {
	  add(keymap, "jnpad-undo.shortcut"                     , Accelerators.UNDO                     , undoAction);
	  add(keymap, "jnpad-redo.shortcut"                     , Accelerators.REDO                     , redoAction);
	  add(keymap, "jnpad-select-line.shortcut"              , Accelerators.SELECT_LINE              , get(DefaultEditorKit.selectLineAction));
	  add(keymap, "jnpad-selection-begin-line.shortcut"     , Accelerators.SELECTION_BEGIN_LINE     , get(DefaultEditorKit.selectionBeginLineAction));
	  add(keymap, "jnpad-selection-end-line.shortcut"       , Accelerators.SELECTION_END_LINE       , get(DefaultEditorKit.selectionEndLineAction));
	  add(keymap, "jnpad-select-paragraph.shortcut"         , Accelerators.SELECT_PARAGRAPH         , get(DefaultEditorKit.selectParagraphAction));
	  add(keymap, "jnpad-selection-begin-paragraph.shortcut", Accelerators.SELECTION_BEGIN_PARAGRAPH, get(DefaultEditorKit.selectionBeginParagraphAction));
	  add(keymap, "jnpad-selection-end-paragraph.shortcut"  , Accelerators.SELECTION_END_PARAGRAPH  , get(DefaultEditorKit.selectionEndParagraphAction));
	  add(keymap, "jnpad-select-word.shortcut"              , Accelerators.SELECT_WORD              , selectWordAction);
	  add(keymap, "jnpad-delete-line.shortcut"              , Accelerators.DELETE_LINE              , deleteLineAction);
	  add(keymap, "jnpad-deletion-begin-line.shortcut"      , Accelerators.DELETION_BEGIN_LINE      , deletionBeginLineAction);
	  add(keymap, "jnpad-deletion-end-line.shortcut"        , Accelerators.DELETION_END_LINE        , deletionEndLineAction);
	  add(keymap, "jnpad-delete-paragraph.shortcut"         , Accelerators.DELETE_PARAGRAPH         , deleteParagraphAction);
	  add(keymap, "jnpad-deletion-begin-paragraph.shortcut" , Accelerators.DELETION_BEGIN_PARAGRAPH , deletionBeginParagraphAction);
	  add(keymap, "jnpad-deletion-end-paragraph.shortcut"   , Accelerators.DELETION_END_PARAGRAPH   , deletionEndParagraphAction);
	  add(keymap, "jnpad-delete-word.shortcut"              , Accelerators.DELETE_WORD              , deleteWordAction);
	  add(keymap, "jnpad-copy-line.shortcut"                , Accelerators.COPY_LINE                , copyLineAction);
	  add(keymap, "jnpad-cut-line.shortcut"                 , Accelerators.CUT_LINE                 , cutLineAction);
	  add(keymap, "jnpad-highlight-all-occurrences.shortcut", Accelerators.HIGHLIGHT_ALL_OCCURRENCES, highlightAllOccurrencesAction);
	  add(keymap, "jnpad-clear-all-occurrences.shortcut"    , Accelerators.CLEAR_ALL_OCCURRENCES    , clearAllOccurrencesAction);
	  add(keymap, "jnpad-next-occurrence.shortcut"          , Accelerators.NEXT_OCCURRENCE          , nextOccurrenceAction);
	  add(keymap, "jnpad-previous-occurrence.shortcut"      , Accelerators.PREVIOUS_OCCURRENCE      , previousOccurrenceAction);
	  add(keymap, "jnpad-right-indent.shortcut"             , Accelerators.RIGHT_INDENT             , rightIndentAction);
	  add(keymap, "jnpad-left-indent"                       , Accelerators.LEFT_INDENT              , leftIndentAction);
	  add(keymap, "jnpad-increase-font.shortcut"            , Accelerators.INCREASE_FONT            , increaseFontAction);
	  add(keymap, "jnpad-decrease-font.shortcut"            , Accelerators.DECREASE_FONT            , decreaseFontAction);
	  add(keymap, "jnpad-complete-word.shortcut"            , Accelerators.COMPLETE_WORD            , completeWordAction);
	  add(keymap, "jnpad-complete-word-all.shortcut"        , Accelerators.COMPLETE_WORD_ALL        , completeWordAllAction);
	  add(keymap, "jnpad-spaces-to-tabs.shortcut"           , Accelerators.SPACES_TO_TABS           , spacesToTabsAction);
	  add(keymap, "jnpad-tabs-to-spaces.shortcut"           , Accelerators.TABS_TO_SPACES           , tabsToSpacesAction);
    }

    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER    , 0), insertBreakAction);
    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT   , 0), toggleTextModeAction);
    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_CAPS_LOCK, 0), capsLockAction);
    keymap.addActionForKeyStroke(KeyStroke.getKeyStroke(KeyEvent.VK_NUM_LOCK , 0), numLockAction);
  }
  
  /**
   * 
   * @param keymap Keymap
   * @param key String
   * @param def KeyStroke
   * @param action Action
   */ 
  private static void add(Keymap keymap, String key, KeyStroke def, Action action) {
    if (Config.isUsingCustomShortcuts()) {
      KeyStroke ks = Accelerators.getPropAccelerator(key, null);
      if (ks != null) {
        keymap.addActionForKeyStroke(ks, action);
      }
    }
    else {
      keymap.addActionForKeyStroke(Accelerators.getPropAccelerator(key, def), action);
    }
  }

  /**
   * Return an action with a given name.
   *
   * @param name String
   * @return Action
   */
  public Action get(String name) {
    return actions.get(name);
  }

  /**
   *
   * @param textComponent JTextComponent
   */
  private void createActionTable(JTextComponent textComponent) {
    // get all actions into arrays
    Action[] textActions = textComponent.getActions();
    
    overrideActions = new Action[] {
        insertBreakAction = new InsertBreakAction(),
        selectWordAction  = new SelectWordAction(),
        new NextWordAction(false),
        new NextWordAction(true),
        new PreviousWordAction(false),
        new PreviousWordAction(true),
        new EndWordAction(false),
        new EndWordAction(true),
        new BeginWordAction(false),                
        new BeginWordAction(true),
    };

    Action[] myActions = {
		undoAction                   = new UndoAction(),
		redoAction                   = new RedoAction(),
		deleteLineAction             = new DeleteLineAction(),
		deletionBeginLineAction      = new DeletionBeginLineAction(),
		deletionEndLineAction        = new DeletionEndLineAction(),
		deleteParagraphAction        = new DeleteParagraphAction(),
		deletionBeginParagraphAction = new DeletionBeginParagraphAction(),
		deletionEndParagraphAction   = new DeletionEndParagraphAction(),
		deleteWordAction             = new DeleteWordAction(),
		copyLineAction               = new CopyLineAction(),
		cutLineAction                = new CutLineAction(),
		highlightAllOccurrencesAction= new HighlightAllOccurrencesAction(),
		clearAllOccurrencesAction    = new ClearAllOccurrencesAction(),
		nextOccurrenceAction         = new NextOccurrenceAction(),
		previousOccurrenceAction     = new PreviousOccurrenceAction(),
		rightIndentAction            = new RightIndentAction(),
		leftIndentAction             = new LeftIndentAction(),
		increaseFontAction           = new IncreaseFontAction(),
		decreaseFontAction           = new DecreaseFontAction(),
		toggleTextModeAction         = new ToggleTextModeAction(),
        capsLockAction               = new CapsLockAction(),
        numLockAction                = new NumLockAction(),
		tabsToSpacesAction           = new TabsToSpacesAction(),
		spacesToTabsAction           = new SpacesToTabsAction(),
		completeWordAction           = new CompleteWordAction(),
		completeWordAllAction        = new CompleteWordAllAction(),
    };

    actions = new HashMap<Object, Action>();

    for (Action action : textActions) {
      actions.put(action.getValue(Action.NAME), action);
    }
    for (Action action : overrideActions) {
      actions.put(action.getValue(Action.NAME), action);
    }
    for (Action action : myActions) {
      actions.put(action.getValue(Action.NAME), action);
    }
  }

  /**
   *
   * @param b boolean
   */
  public void setUndoEnabled(boolean b) {
    undoAction.setEnabled(b);
  }

  /**
   *
   * @param b boolean
   */
  public void setRedoEnabled(boolean b) {
    redoAction.setEnabled(b);
  }

  /**
   * 
   * @param editable boolean
   */
  public void setEditable(final boolean editable) {
    for (Object key : actions.keySet()) {
      Action a = actions.get(key);
      if (a instanceof IEditAction) {
        a.setEnabled(editable);
      }
    }
  }
  
  //////////////////////////////////////////////////////////////////////////////
  static class UndoAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 1413908501994140495L;

    /**
     *
     */
    UndoAction() {
      super(ACTION_NAME_UNDO);
      setEnabled(false);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        Buffer buffer = jNPad.getActiveBuffer();
        try {
          if (buffer != null && buffer.canUndo()) {
            buffer.undo();
          }
        }
        catch (CannotRedoException ex) {
          LOGGER.log(Level.WARNING, ex.getMessage(), ex);
        }
        finally {
          jNPad.updateControls(Updatable.CTRLS_UNDO | Updatable.CTRLS_TEXT_CHANGED);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class RedoAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -8402376403958102330L;

    /**
     *
     */
    RedoAction() {
      super(ACTION_NAME_REDO);
      setEnabled(false);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        Buffer buffer = jNPad.getActiveBuffer();
        try {
          if (buffer != null && buffer.canRedo()) {
            buffer.redo();
          }
        }
        catch (CannotRedoException ex) {
          LOGGER.log(Level.WARNING, ex.getMessage(), ex);
        }
        finally {
          jNPad.updateControls(Updatable.CTRLS_UNDO | Updatable.CTRLS_TEXT_CHANGED);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  private static abstract class JNPadTextActionWithOrWithoutSelection extends JNPadTextAction {
    private boolean           withSelection;

    /** UID */
    private static final long serialVersionUID = -2473760499325530848L;

    /**
     * 
     * @param actionName String
     * @param withSelection boolean
     */
    protected JNPadTextActionWithOrWithoutSelection(String actionName, boolean withSelection) {
      super(actionName);
      this.withSelection = withSelection;
    }

    /**
     * 
     * @param c JTextComponent
     * @param pos int
     */
    protected void moveCaret(JTextComponent c, int pos) {
      if (withSelection) {
        c.getCaret().moveDot(pos);
      }
      else {
        c.setCaretPosition(pos);
      }
    }
  }  
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class BeginWordAction extends JNPadTextActionWithOrWithoutSelection {
    /** UID */
    private static final long serialVersionUID = -816193758794410697L;

    /**
     * Instantiates a new begin word action.
     *
     * @param withSelection the with selection
     */
    BeginWordAction(boolean withSelection) {
      super(withSelection ? DefaultEditorKit.selectionBeginWordAction : DefaultEditorKit.beginWordAction, withSelection);
    }
    
    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          String delimiters = textArea.getDelimiters();
          int offs = textArea.getCaretPosition();
          final String text = textArea.getDocument().getText(0, offs);
          while (offs > 0) {
            char chPrev = text.charAt(offs - 1);
            if ((delimiters.indexOf(chPrev) >= 0) || (Character.isWhitespace(chPrev))) {
              break;
            }
            --offs;
            if (offs == 0)
              break; // otherwise offs-1 below generates an index out of bounds
            char ch = text.charAt(offs);
            chPrev = text.charAt(offs - 1);
            if ((delimiters.indexOf(ch) >= 0) || (delimiters.indexOf(chPrev) >= 0)
                  || Character.isWhitespace(ch) || Character.isWhitespace(chPrev)) {
              break;
            }
          }
          moveCaret(textArea, offs);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class EndWordAction extends JNPadTextActionWithOrWithoutSelection {
    /** UID */
    private static final long serialVersionUID = -1766369979956403270L;

    /**
     * Instantiates a new end word action.
     *
     * @param withSelection the with selection
     */
    EndWordAction(boolean withSelection) {
      super(withSelection ? DefaultEditorKit.selectionEndWordAction : DefaultEditorKit.endWordAction, withSelection);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          String delimiters = textArea.getDelimiters();
          int offs = textArea.getCaretPosition();
          final int iOffs = offs;
          final String text = textArea.getDocument().getText(iOffs, textArea.getDocument().getLength() - iOffs);
          while ((offs - iOffs) < text.length() - 1) {
            ++offs;
            char ch = text.charAt(offs - iOffs);
            if ((delimiters.indexOf(ch) >= 0) || Character.isWhitespace(ch)) {
              break;
            }
          }
          moveCaret(textArea, offs);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class PreviousWordAction extends JNPadTextActionWithOrWithoutSelection {
    /** UID */
    private static final long serialVersionUID = -5804423781790119209L;

    /**
     * Instantiates a new previous word action.
     *
     * @param withSelection the with selection
     */
    PreviousWordAction(boolean withSelection) {
      super(withSelection ? DefaultEditorKit.selectionPreviousWordAction : DefaultEditorKit.previousWordAction, withSelection);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          String delimiters = textArea.getDelimiters();
          int offs = textArea.getCaretPosition();
          final String text = textArea.getDocument().getText(0, offs);
          while (offs > 0) {
            --offs;
            char ch = text.charAt(offs);
            char chPrev = text.charAt(offs - 1);
            if (Character.isWhitespace(ch) && Character.isWhitespace(chPrev) && ch != Utilities.LF)
              continue;
            else if (delimiters.indexOf(ch) >= 0 || delimiters.indexOf(chPrev) >= 0 ||
                (offs >= 2 && Character.isWhitespace(chPrev) && !Character.isWhitespace(text.charAt(offs - 2)) && ch != Utilities.LF))
              break;
            else if (Character.isWhitespace(chPrev) && !Character.isWhitespace(ch))
              break;
            else if (!Character.isWhitespace(chPrev) && ch == Utilities.LF)
              break;
            else if (Character.isWhitespace(chPrev) && ch == Utilities.LF) { // compensate for space at the end of a line
              while (Character.isWhitespace(chPrev) && (offs > 0)) {
                --offs;
                ch = text.charAt(offs);
                chPrev = text.charAt(offs - 1);
              }
              break;
            }
          }
          moveCaret(textArea, offs);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////
  
  //////////////////////////////////////////////////////////////////////////////
  static class NextWordAction extends JNPadTextActionWithOrWithoutSelection {
    /** UID */
    private static final long serialVersionUID = -5804423781790119209L;

    /**
     * Instantiates a new next word action.
     *
     * @param withSelection the with selection
     */
    NextWordAction(boolean withSelection) {
      super(withSelection ? DefaultEditorKit.selectionNextWordAction : DefaultEditorKit.nextWordAction, withSelection);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          String delimiters = textArea.getDelimiters();
          int offs = textArea.getCaretPosition();
          final int iOffs = offs;
          final String text = textArea.getDocument().getText(iOffs, textArea.getDocument().getLength() - iOffs);
          final int len = text.length();
          while ((offs - iOffs) < len) {
            ++offs;
            if (offs - iOffs == len)
              break;
            char ch = text.charAt(offs - iOffs);
            char chPrev = text.charAt(offs - iOffs - 1);
            if ((delimiters.indexOf(ch) >= 0) ||
                (delimiters.indexOf(chPrev) >= 0) ||
                Character.isWhitespace(chPrev) ||
                ch == Utilities.LF) {
              while ((offs - iOffs < len) && Character.isWhitespace(ch) && ch != Utilities.LF) {
                if (delimiters.indexOf(chPrev) >= 0)
                  break;
                ++offs;
                ch = text.charAt(offs - iOffs);
              }
              if (ch == Utilities.LF && Character.isWhitespace(text.charAt(offs - iOffs - 1)))
                continue;
              break;
            }
            //used to fix incorrect behavior when a space is at the end of a line
            if (!Character.isWhitespace(chPrev) && Character.isWhitespace(ch)) {
              int offs0 = offs;
              while ((offs - iOffs) < (len - 1) && ch != Utilities.LF && Character.isWhitespace(ch)) {
                ++offs;
                ch = text.charAt(offs - iOffs);
                chPrev = text.charAt(offs - iOffs - 1);
              }
              offs = offs0;
              if (ch == Utilities.LF)
                break;
              continue;
            }
          }
          moveCaret(textArea, offs);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////


  //////////////////////////////////////////////////////////////////////////////
  static class SelectWordAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = 3927036646628861226L;
    
    /**
     *
     */
    SelectWordAction() {
      super(DefaultEditorKit.selectWordAction);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          String delimiters = textArea.getDelimiters();
          int origPos = textArea.getCaret().getDot();
          int[] range = TextUtilities.getWordBoundsAt(textArea.getDocument(), origPos, delimiters);
          if (range == null) {
            return;
          }
          textArea.getCaret().setDot(range[0]);
          textArea.getCaret().moveDot(range[1]);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);

        Action start = new BeginWordAction(false);
        Action end = new EndWordAction(true);
        start.actionPerformed(e);
        end.actionPerformed(e);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeleteLineAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 3635891954489427968L;

    /**
     *
     */
    DeleteLineAction() {
      super(ACTION_NAME_DELETE_LINE);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectLineAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e); // target.replaceSelection(null);

          int lineCount = TextUtilities.getNumberOfLines(target.getDocument());
          int lineNumbr = TextUtilities.getLineNumber(target.getDocument(), target.getCaretPosition());

          if (lineNumbr < lineCount - 1) {
            get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e);
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeletionBeginLineAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 8754448756437935965L;

    /**
     *
     */
    DeletionBeginLineAction() {
      super(ACTION_NAME_DELETION_BEGIN_LINE);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectionBeginLineAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e); // target.replaceSelection(null);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeletionEndLineAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 7656879384815607333L;

    /**
     *
     */
    DeletionEndLineAction() {
      super(ACTION_NAME_DELETION_END_LINE);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectionEndLineAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e); // target.replaceSelection(null);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeleteWordAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -6227149433396832606L;

    /**
     * Instantiates a new delete word action.
     */
    DeleteWordAction() {
      super(ACTION_NAME_DELETE_WORD);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectWordAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e); // target.replaceSelection(null);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeleteParagraphAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -2003966419296180385L;

    /**
     *
     */
    DeleteParagraphAction() {
      super(ACTION_NAME_DELETE_PARAGRAPH);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectParagraphAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeletionBeginParagraphAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 7821147495101266649L;

    /**
     *
     */
    DeletionBeginParagraphAction() {
      super(ACTION_NAME_DELETION_BEGIN_PARAGRAPH);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectionBeginParagraphAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class DeletionEndParagraphAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 7031727398621792604L;

    /**
     *
     */
    DeletionEndParagraphAction() {
      super(ACTION_NAME_DELETION_END_PARAGRAPH);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectionEndParagraphAction).actionPerformed(e);
          get(DefaultEditorKit.deleteNextCharAction).actionPerformed(e);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class CopyLineAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = -1763465116999117170L;

    /**
     * Instantiates a new copy line action.
     */
    CopyLineAction() {
      super(ACTION_NAME_COPY_LINE);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectLineAction).actionPerformed(e);
          get(DefaultEditorKit.copyAction).actionPerformed(e);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class CutLineAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -8555512203267088983L;

    /**
     *
     */
    CutLineAction() {
      super(ACTION_NAME_CUT_LINE);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JTextComponent target = getTextComponent(e);
        if (target != null) {
          get(DefaultEditorKit.selectLineAction).actionPerformed(e);
          get(DefaultEditorKit.cutAction).actionPerformed(e);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class HighlightAllOccurrencesAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = -5846207806975730585L;

    /**
     *
     */
    HighlightAllOccurrencesAction() {
      super(ACTION_NAME_HIGHLIGHT_ALL_OCCURRENCES);
      setEnabled(Config.OCCURRENCES_HIGHLIGHTER_VISIBLE.getValue());
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        EditPane editPane = jNPad.getActiveEditPane();

        if (editPane != null) {
          JNPadTextArea textArea = editPane.getTextArea();
          String selectedText;
          if (textArea.hasSelection()) {
            selectedText = textArea.getSelectedText();
          }
          else {
            Document doc = textArea.getDocument();
            // detect the word at the cursor
            selectedText = TextUtilities.getSingleWordAt(doc, textArea.getCaretPosition(), textArea.getDelimiters());
          }
          if (selectedText != null) {
            editPane.highlightAllOccurrences(new SearchContext(selectedText));
          }
          else {
            editPane.clearAllOccurrences();
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class ClearAllOccurrencesAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = -3505149505020895583L;

    /**
     *
     */
    ClearAllOccurrencesAction() {
      super(ACTION_NAME_CLEAR_ALL_OCCURRENCES);
      setEnabled(Config.OCCURRENCES_HIGHLIGHTER_VISIBLE.getValue());
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        EditPane editPane = jNPad.getActiveEditPane();
        if (editPane != null) {
          editPane.clearAllOccurrences();
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class NextOccurrenceAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = 8266484094729197897L;

    /**
     *
     */
    NextOccurrenceAction() {
      super(ACTION_NAME_NEXT_OCCURRENCE);
      setEnabled(Config.OCCURRENCES_HIGHLIGHTER_VISIBLE.getValue());
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        EditPane editPane = jNPad.getActiveEditPane();
        if (editPane != null) {
          editPane.nextOccurrence();
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class PreviousOccurrenceAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = 3843284508521172572L;

    /**
     *
     */
    PreviousOccurrenceAction() {
      super(ACTION_NAME_PREVIOUS_OCCURRENCE);
      setEnabled(Config.OCCURRENCES_HIGHLIGHTER_VISIBLE.getValue());
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        EditPane editPane = jNPad.getActiveEditPane();

        if (editPane != null) {
          editPane.previousOccurrence();
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }

  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class RightIndentAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -6949965516937583077L;

    /**
     * Instantiates a new right indent action.
     */
    RightIndentAction() {
      super(ACTION_NAME_RIGHT_INDENT);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          if (textArea.getUseTabs()) {
            if (textArea.hasSelection()) {
              TextUtilities.indentLines(textArea.getDocument(), textArea.getSelectionStart(), textArea.getSelectionEnd());
            }
            else {
              get(DefaultEditorKit.insertTabAction).actionPerformed(e);
            }
          }
          else {
            int tabSize = textArea.getTabSize();
            if (textArea.hasSelection()) {
              TextUtilities.indentLines(textArea.getDocument(), textArea.getSelectionStart(), textArea.getSelectionEnd(), tabSize);
            }
            else {
              textArea.insert(Utilities.spaces(tabSize), textArea.getCaretPosition());
            }
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  class LeftIndentAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -6088245184514079552L;

    /**
     * Instantiates a new left indent action.
     */
    LeftIndentAction() {
      super(ACTION_NAME_LEFT_INDENT);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          if (textArea.getUseTabs()) {
            if (textArea.hasSelection()) {
              TextUtilities.unindentLines(textArea.getDocument(), textArea.getSelectionStart(), textArea.getSelectionEnd());
            }
            else {
              get(DefaultEditorKit.deletePrevCharAction).actionPerformed(e);
            }
          }
          else {
            int tabSize = textArea.getTabSize();
            if (textArea.hasSelection()) {
              TextUtilities.unindentLines(textArea.getDocument(), textArea.getSelectionStart(), textArea.getSelectionEnd(), tabSize);
            }
            else {
              for (int i = 0; i < tabSize; i++) {
                get(DefaultEditorKit.deletePrevCharAction).actionPerformed(e);
              }
            }
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class IncreaseFontAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = 6321699285684527213L;

    /**
     * Instantiates a new increase font action.
     */
    IncreaseFontAction() {
      super(ACTION_NAME_INCREASE_FONT);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JTextComponent target = getTextComponent(e);
        if (target != null) {
          Font font = target.getFont();
          Font newFont = font.deriveFont( (float) (font.getSize() + 1));

          JNPadFrame jNPad = getJNPadParent(e);
          Buffer buffer = jNPad.getActiveBuffer();
          if (buffer != null) {
            buffer.setEditorFont(newFont);
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class DecreaseFontAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = -6358470088719501208L;

    /**
     *
     */
    DecreaseFontAction() {
      super(ACTION_NAME_DECREASE_FONT);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JTextComponent target = getTextComponent(e);
        if (target != null) {
          Font font = target.getFont();
          Font newFont = font.deriveFont( (float) (font.getSize() - 1));

          JNPadFrame jNPad = getJNPadParent(e);
          Buffer buffer = jNPad.getActiveBuffer();
          if (buffer != null) {
            buffer.setEditorFont(newFont);
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class InsertBreakAction extends JNPadTextAction /*implements IEditAction*/ { // [bug 2015-07-11]
    /** UID */
    private static final long serialVersionUID = -3288060714579447443L;

    /**
     * Instantiates a new insert break action.
     */
    InsertBreakAction() {
      super(DefaultEditorKit.insertBreakAction);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JNPadTextArea textArea = getTextArea(e);

        if (textArea == null) {
          return;
        }

        if ( (!textArea.isEditable()) || (!textArea.isEnabled())) {
          UIManager.getLookAndFeel().provideErrorFeedback(textArea);
          return;
        }

        if (textArea.getAutoIndent()) {
          final int selectionStart = textArea.getSelectionStart();
          final int selectionEnd = textArea.getSelectionEnd();

          if (selectionEnd == 0) { // primer caracter
            textArea.replaceSelection(Utilities.LF_STRING);
            return;
          }

          Document doc = textArea.getDocument();

          Element lineElem = TextUtilities.getLine(doc, TextUtilities.getLineNumber(doc, selectionStart));

          final int startOffset = lineElem.getStartOffset();
          final int length = selectionStart - startOffset;

          if (length == 0) { // primer caracter de línea
            textArea.replaceSelection(Utilities.LF_STRING);
            return;
          }

          try {
            Segment lineHead = new Segment();
            doc.getText(startOffset, length, lineHead);
            StringBuilder spaces = new StringBuilder();
            for (int i = lineHead.offset; i < lineHead.offset + lineHead.count; i++) {
              if (lineHead.array[i] == Utilities.SPACE) {
                spaces.append(Utilities.SPACE_STRING);
              }
              else if (lineHead.array[i] == Utilities.TAB) {
                spaces.append(Utilities.TAB_STRING);
              }
              else {
                break;
              }
            }

            String lastChar = doc.getText(selectionEnd - 1, 1);
            String nextChar = null;
            try {
              nextChar = doc.getText(selectionEnd, 1);
            }
            catch (StringIndexOutOfBoundsException strexc) {
              LOGGER.log(Level.FINE, strexc.getMessage(), strexc);
            }

            int _position_;
            String _spaces_ = spaces.toString();
            boolean _open_bracket_= false;

            if (Scheme.hasCLikeSyntax(textArea.getContentType()) && Config.TEXT_AUTO_CLOSE_BRACKETS.getValue()) {
              if (lastChar.equals("{") && (nextChar == null || !nextChar.equals("}"))) { //$NON-NLS-1$ //$NON-NLS-2$
                if (textArea.getUseTabs())
                  spaces.append(Utilities.TAB_STRING);
                else
                  spaces.append(Utilities.spaces(textArea.getTabSize()));
                _open_bracket_ = lastChar.equals("{"); //$NON-NLS-1$
              }
            }

            textArea.replaceSelection(Utilities.LF_STRING + spaces);
            textArea.setCaretPosition(_position_ = selectionStart + spaces.length() + 1);
            
            if(_open_bracket_) {
              textArea.insert(Utilities.LF_STRING + _spaces_ + "}", _position_); //$NON-NLS-1$
              textArea.setCaretPosition(_position_);
            }
            
          }
          catch (Exception ex) {
            LOGGER.log(Level.WARNING, ex.getMessage(), ex);
            textArea.replaceSelection(Utilities.LF_STRING);
          }
        }
        else {
          textArea.replaceSelection(Utilities.LF_STRING);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class ToggleTextModeAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 1561152749491506327L;

    /**
     *
     */
    ToggleTextModeAction() {
      super(ACTION_NAME_TOGGLE_TEXT_MODE);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        JNPadTextArea textArea = jNPad.getActiveTextArea();
        if (textArea != null) {
          boolean b = !textArea.isOverwriteTextMode();
          textArea.setOverwriteTextMode(b);
          jNPad.getStatusBar().setAsOverwriteTextMode(b);
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class CapsLockAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = -174260352478116902L;

    /**
     *
     */
    CapsLockAction() {
      super(ACTION_NAME_CAPS_LOCK);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        try {
          jNPad.getStatusBar().setCapsLockIndicatorEnabled(jNPad.getToolkit().getLockingKeyState(KeyEvent.VK_CAPS_LOCK));
        }
        catch (UnsupportedOperationException uoe) {
          //empty
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class NumLockAction extends JNPadTextAction {
    /** UID */
    private static final long serialVersionUID = 792328569197293370L;

    /**
     *
     */
    NumLockAction() {
      super(ACTION_NAME_NUM_LOCK);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        try {
          jNPad.getStatusBar().setNumLockIndicatorEnabled(jNPad.getToolkit().getLockingKeyState(KeyEvent.VK_NUM_LOCK));
        }
        catch (UnsupportedOperationException uoe) {
          //empty
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class SpacesToTabsAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = 3713190328770441006L;

    /**
     * Instantiates a new spaces to tabs action.
     */
    SpacesToTabsAction() {
      super(ACTION_NAME_SPACES_TO_TABS);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          Document doc = textArea.getDocument();
          Element map = doc.getDefaultRootElement();
          int count = map.getElementCount();
          int tabSize = textArea.getTabSize();
          for (int i = 0; i < count; i++) {
            Element lineElement = map.getElement(i);
            int start = lineElement.getStartOffset();
            int end = lineElement.getEndOffset() - 1;
            end -= start;
            String text = TextUtilities.doSpacesToTabs(TextUtilities.getText(doc, start, end), tabSize);
            doc.remove(start, end);
            doc.insertString(start, text, null);
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class TabsToSpacesAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -2351345392416101941L;

    /**
     *
     */
    TabsToSpacesAction() {
      super(ACTION_NAME_TABS_TO_SPACES);
    }

    /**
     * Action performed.
     *
     * @param e ActionEvent
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      Buffer buffer = null;
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        buffer = jNPad.getActiveBuffer();

        if (buffer != null) {
          buffer.beginCompoundEdit();
        }

        JNPadTextArea textArea = getTextArea(e);
        if (textArea != null) {
          Document doc = textArea.getDocument();
          Element map = doc.getDefaultRootElement();
          int count = map.getElementCount();
          int tabSize = textArea.getTabSize();
          for (int i = 0; i < count; i++) {
            Element lineElement = map.getElement(i);
            int start = lineElement.getStartOffset();
            int end = lineElement.getEndOffset() - 1;
            end -= start;
            String text = TextUtilities.doTabsToSpaces(TextUtilities.getText(doc, start, end), tabSize);
            doc.remove(start, end);
            doc.insertString(start, text, null);
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      finally {
        if (buffer != null) {
          buffer.endCompoundEdit();
        }
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class CompleteWordAction extends JNPadTextAction implements IEditAction {
    /** UID */
    private static final long serialVersionUID = -8404878066287152329L;

    /**
     *
     */
    CompleteWordAction() {
      super(ACTION_NAME_COMPLETE_WORD);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        JNPadTextArea textArea = jNPad.getActiveTextArea();
        if (textArea != null) {
          String word = CompletionUtilities.getWord(textArea, textArea.getCaretPosition());
          if (Utilities.isBlankString(word))
            return;

          jNPad.showWaitCursor();

          TreeSet<String> completions = new TreeSet<String>();

          int wordLen = word.length();

          CompletionUtilities.set(textArea, word, wordLen, textArea.getDelimiters(), completions);

          if (completions.size() > 1) {
            int endIndex = String.valueOf(completions.first()).length();
              for (String completion : completions) {
                  endIndex = Math.min(endIndex,
                          CompletionUtilities.getDivergentIndex(String.valueOf(completions.first()), String.valueOf(completion)));
              }

            jNPad.hideWaitCursor();

            if (endIndex > wordLen) {
              textArea.insert(String.valueOf(completions.first()).substring(wordLen, endIndex), textArea.getCaretPosition());
            }
            else {
              new CompletionPopup(textArea, word,
                                   (completions.toArray(new String[completions.size()])));
            }
          }
          else {
            jNPad.hideWaitCursor();
            if (completions.size() == 1) {
              textArea.insert(String.valueOf(completions.first()).substring(wordLen), textArea.getCaretPosition());
            }
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

  //////////////////////////////////////////////////////////////////////////////
  static class CompleteWordAllAction extends JNPadTextAction implements IEditAction {
    /**
     *
     */
    private static final long serialVersionUID = 8081149527394287826L;

    /**
     *
     */
    CompleteWordAllAction() {
      super(ACTION_NAME_COMPLETE_WORD_ALL);
    }

    /**
     * 
     * @param e ActionEvent 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {
      try {
        JNPadFrame jNPad = getJNPadParent(e);
        JNPadTextArea textArea = jNPad.getActiveTextArea();
        if (textArea != null) {
          String word = CompletionUtilities.getWord(textArea, textArea.getCaretPosition());
          if (Utilities.isBlankString(word))
            return;

          TreeSet<String> completions = new TreeSet<String>();

          int wordLen = word.length();

          jNPad.showWaitCursor();

          for (JNPadTextArea etxarea : jNPad.getViewer().getTextAreas()) {
            CompletionUtilities.set(etxarea, word, wordLen, textArea.getDelimiters(), completions);
          }

          if (completions.size() > 1) {
            int endIndex = String.valueOf(completions.first()).length();
              for (String completion : completions) {
                  endIndex = Math.min(endIndex,
                          CompletionUtilities.getDivergentIndex(String.valueOf(completions.first()), String.valueOf(completion)));
              }

            jNPad.hideWaitCursor();

            if (endIndex > wordLen) {
              textArea.insert(String.valueOf(completions.first()).substring(wordLen, endIndex), textArea.getCaretPosition());
            }
            else {
              new CompletionPopup(textArea, word,
                                   (completions.toArray(new String[completions.size()])));
            }
          }
          else {
            jNPad.hideWaitCursor();
            if (completions.size() == 1) {
              textArea.insert(String.valueOf(completions.first()).substring(wordLen), textArea.getCaretPosition());
            }
          }
        }
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////

}
