/*
 * 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;

import static jnpad.util.Utilities.EMPTY_STRING;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Image;
import java.awt.Insets;
import java.awt.SystemTray;
import java.awt.TrayIcon;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.WindowEvent;
import java.awt.print.PageFormat;
import java.awt.print.PrinterJob;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractButton;
import javax.swing.ActionMap;
import javax.swing.Box;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.text.JTextComponent;
import javax.swing.text.Keymap;

import jnpad.action.ActionManager;
import jnpad.action.JNPadAction;
import jnpad.config.Accelerators;
import jnpad.config.Config;
import jnpad.config.Configurable;
import jnpad.config.Controlable;
import jnpad.config.KeyEventWorkaround;
import jnpad.search.FindDialog;
import jnpad.search.FindResultsPanel;
import jnpad.search.IncrementalSearchPanel;
import jnpad.search.ReplaceDialog;
import jnpad.search.Text;
import jnpad.text.Buffer;
import jnpad.text.BufferSet;
import jnpad.text.EditPane;
import jnpad.text.JNPadTextArea;
import jnpad.text.MockBuffer;
import jnpad.text.Viewer;
import jnpad.ui.EdgeBorder;
import jnpad.ui.JNPadFileFilter;
import jnpad.ui.JNPadSplitPane;
import jnpad.ui.icon.IconUtilities;
import jnpad.ui.status.IStatusBar;
import jnpad.ui.status.StatusDisplayable;
import jnpad.util.LineSeparator;
import jnpad.util.Platform;
import jnpad.util.Utilities;

/**
 * The Class JNPadFrame.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public class JNPadFrame extends JFrame implements StatusDisplayable, Controlable {
  JPanel                         contentPane;
  JPanel                         pnBackground          = new JPanel();
  JSplitPane                     sppOutput             = new JNPadSplitPane(JSplitPane.VERTICAL_SPLIT);
  FindResultsPanel               findResultsPanel;
  IncrementalSearchPanel         incrementalSearchPanel;
  JNPadStatusBar                 statusBar;
  JNPadViewer                    viewer;

  // Helpers
  JNPadActionHelper              actions;
  JNPadMenuHelper                menus;
  JNPadToolBarHelper             toolbars;

  // formato de página
  private PageFormat             pageFormat;

  private FindDialog             findDialog;
  private ReplaceDialog          replaceDialog;

  private int                    waitCount;
  private int                    index_file;
  private int                    sppOutputPosition     = -1;
  private boolean                isIncrementalSearchVisible;
  private boolean                isFindResultsVisible;
  private boolean                isResetEnabled        = true;
  private boolean                lastGrobalEditable    = true;
  private Boolean                lastReadOnly;
  private Boolean                lastDirty;

  private boolean                isFullScreen;
  private int                    prevX, prevY, prevWidth, prevHeight;
  @SuppressWarnings("unused")
  private boolean                prevStatusVisible, prevToolbarsVisible, prevIsMaximized;
  private JButton                btFullScreen;
  private Component              horizontalGlue;

  private PropertyChangeListener propertyChangeHandler = new PropertyChangeHandler();

  private List<JNPadInput>       inputFiles;
  private String                 sessionPath;

  private JNPadKeyboardHandler   keyboardHandler;
  private KeyListener            keyEventInterceptor;

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

  /** UID */
  private static final long      serialVersionUID      = 5466384895908813762L;

  /**
   * Instantiates a new jNPad frame.
   * 
   * @param sessionPath the session path
   */
  public JNPadFrame(String sessionPath) {
    this(null, sessionPath);
  }

  /**
   * Instantiates a new jNPad frame.
   * 
   * @param inputFiles the input files
   */
  public JNPadFrame(List<JNPadInput> inputFiles) {
    this(inputFiles, null);
  }
  
  /**
   * Instantiates a new jNPad frame.
   *
   * @param inputFiles the input files
   * @param sessionPath the session path
   */
  public JNPadFrame(List<JNPadInput> inputFiles, String sessionPath) {
    super(JNPad.TITLE);
    try {
      this.inputFiles = inputFiles;
      this.sessionPath = sessionPath;
      
      jbInit();

      validate();

      int x = Config.JNPAD_X.getValue();
      int y = Config.JNPAD_Y.getValue();

      if(x == Config.JNPAD_X.getDefault() || y == Config.JNPAD_Y.getDefault()) {
        setLocationRelativeTo(null); // GUIUtilities.centerComponent(this);
      }
      else {
        setLocation(x, y);
      }

      setVisible(true);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Component initialization.
   *
   * @throws Exception the exception
   */
  private void jbInit() throws Exception {
    try {
      pageFormat = PrinterJob.getPrinterJob().defaultPage();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }

    setSize(Config.JNPAD_WIDTH.getValue(), Config.JNPAD_HEIGHT.getValue());
    
    final List<Image> editorImages = new ArrayList<Image>(3);
    editorImages.add(GUIUtilities.getIcon("icon-jnpad16.png", false).getImage()); //$NON-NLS-1$
    editorImages.add(GUIUtilities.getIcon("icon-jnpad32.png", false).getImage()); //$NON-NLS-1$
    editorImages.add(GUIUtilities.getIcon("icon-jnpad48.png", false).getImage()); //$NON-NLS-1$
    setIconImages(editorImages);
    
    if (Accelerators.isUsingCompositeShortcuts()) {
      keyboardHandler = new JNPadKeyboardHandler();
    }

    actions          = new JNPadActionHelper(this);
    menus            = new JNPadMenuHelper(this);
    toolbars         = new JNPadToolBarHelper(this); 
    statusBar        = new JNPadStatusBar(this);
    findResultsPanel = new FindResultsPanel(this);
    if(Config.TAB_TABBED.getValue()) {
      viewer    = new JNPadTabbedViewer(this, menus.mWindow);
    }
    else {
      viewer    = new JNPadCardViewer(this, menus.mWindow);
    }

    contentPane = (JPanel)this.getContentPane();
    contentPane.setLayout(new BorderLayout());
    contentPane.add(toolbars.toolBar, BorderLayout.NORTH);
    contentPane.add(pnBackground, BorderLayout.CENTER);
    contentPane.add(statusBar, BorderLayout.SOUTH);

    sppOutput.setBottomComponent(findResultsPanel);

    setJMenuBar(menus.getMenuBar());
    
    if(Config.isDefaultMode() || Config.isBasicMode()) {
      setToolBars();
      if(!Config.TOOLBAR_VISIBLE.getValue()) 
        setToolBarVisible(false);
    }

    pnBackground.setLayout(new BorderLayout());
    pnBackground.add(viewer, BorderLayout.CENTER);

    clearStatus();

    menus.loadRecentFiles();
    
    // --- load files --
    if (sessionPath != null) {
      JNPadParser parser = new JNPadParser(sessionPath);
      try {
        parser.load();
      }
      catch(Exception ex) {
        setStatus(StatusType.ERROR, ex.getMessage());
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
      if(inputFiles == null) {
        inputFiles = new ArrayList<JNPadInput>();
      }
      inputFiles.addAll(parser.getJNPadInputList());
    }
    if (!loadOpenedFiles() && Utilities.isEmptyList(inputFiles)) {
      doNewFile(false);
    }
    else if (Utilities.isNotEmptyList(inputFiles)) {
      openFiles(inputFiles);
    }
    // ---

    if(Config.JNPAD_MAXIMIZE.getValue()) 
      setExtendedState(MAXIMIZED_BOTH);

    actions.registerKeyboardActions();
    
    if(!Config.STATUSBAR_VISIBLE.getValue()) 
      setStatusBarVisible(false);
    
    if (Config.isDistractionFreeMode()) {
      setUndecorated(true);
      //menus.getMenuBar().setVisible(false);
    }
  }

  /**
   * Gets the keyboard handler.
   *
   * @return the keyboard handler
   */
  public JNPadKeyboardHandler getKeyboardHandler() {
    return keyboardHandler;
  }

  /**
   * Returns the listener that will handle all key events in this view, if any.
   * 
   * @return KeyListener
   */
  public final KeyListener getKeyEventInterceptor() {
    return keyEventInterceptor;
  }

  /**
   * Sets the listener that will handle all key events in this view. For
   * example, the complete word command uses this so that all key events are
   * passed to the word list popup while it is visible.
   * 
   * @param listener KeyListener
   */
  public void setKeyEventInterceptor(KeyListener listener) {
    this.keyEventInterceptor = listener;
  }

  /**
   * @param e KeyEvent
   * @see java.awt.Component#processKeyEvent(java.awt.event.KeyEvent)
   */
  @Override
  protected void processKeyEvent(KeyEvent e) {
    if (!Accelerators.isUsingCompositeShortcuts()) {
      super.processKeyEvent(e);
      return;
    }

    if (getFocusOwner() instanceof JComponent) {
      JComponent comp = (JComponent) getFocusOwner();
      InputMap   im   = comp.getInputMap();
      ActionMap  am   = comp.getActionMap();

      if (im != null && am != null && comp.isEnabled()) {
        Object binding = im.get(KeyStroke.getKeyStrokeForEvent(e));
        if (binding != null && am.get(binding) != null) {
          return;
        }
      }
    }

    if (getFocusOwner() instanceof JTextComponent) {
      if (e.getID() == KeyEvent.KEY_PRESSED) {
        switch (e.getKeyCode()) {
          case KeyEvent.VK_BACK_SPACE:
          case KeyEvent.VK_TAB:
          case KeyEvent.VK_ENTER:
            return;
        }
      }

      Keymap keymap = ((JTextComponent) getFocusOwner()).getKeymap();
      if (keymap.getAction(KeyStroke.getKeyStrokeForEvent(e)) != null) {
        return;
      }
    }

    if (e.isConsumed()) {
      return;
    }

    e = KeyEventWorkaround.processKeyEvent(e);
    if (e == null) {
      return;
    }

    switch (e.getID()) {
      case KeyEvent.KEY_TYPED:
        if (keyEventInterceptor != null) {
          keyEventInterceptor.keyTyped(e);
        }
        else if (keyboardHandler != null) {
          keyboardHandler.keyTyped(e);
        }
        break;
        
      case KeyEvent.KEY_PRESSED:
        if (keyEventInterceptor != null) {
          keyEventInterceptor.keyPressed(e);
        }
        else if (keyboardHandler != null) {
          keyboardHandler.keyPressed(e);
        }
        break;
        
      case KeyEvent.KEY_RELEASED:
        if (keyEventInterceptor != null) {
          keyEventInterceptor.keyReleased(e);
        }
        else if (keyboardHandler != null) {
          keyboardHandler.keyReleased(e);
        }
        break;
      
      default: // Keep FindBugs happy 
        break;
    }

    if (!e.isConsumed()) {
      super.processKeyEvent(e);
    }
  }
  
  /**
   *
   * @return boolean
   */
  public boolean isFullScreen() {
    return isFullScreen;
  }

  /**
   *
   */
  public void toggleFullScreen() {
    if (!isFullScreen) {
      prevX               = getX();
      prevY               = getY();
      prevWidth           = getWidth();
      prevHeight          = getHeight();
      prevStatusVisible   = isStatusBarVisible();
      prevToolbarsVisible = isToolBarVisible();
      prevIsMaximized     = getExtendedState() == MAXIMIZED_BOTH;

      dispose(); //Destroys the whole JFrame but keeps organized every Component
      //Needed if you want to use Undecorated JFrame
      //dispose() is the reason that this trick doesn't work with videos
      setUndecorated(true);
      
      clearStatus();

      //setToolBarVisible(false); [14-08-20]
      //setStatusBarVisible(false); [14-08-20]

      if(btFullScreen == null) {
        btFullScreen = new JButton(actions.fullScreenAction);
        btFullScreen.setText(actions.fullScreenAction.getLabel());
        btFullScreen.setMargin(new Insets(0, 1, 0, 1));
        btFullScreen.setFocusPainted(false);

        horizontalGlue = Box.createHorizontalGlue();
      }
      menus.menuBar.add(horizontalGlue);
      menus.menuBar.add(btFullScreen);

      setBounds(0, 0, getToolkit().getScreenSize().width, getToolkit().getScreenSize().height);
      setVisible(true);
      isFullScreen = true;
    }
    else {
      setVisible(true);

      setBounds(prevX, prevY, prevWidth, prevHeight);
      dispose();
      setUndecorated(false);

      //setToolBarVisible(prevToolbarsVisible); [14-08-20]
      //setStatusBarVisible(prevStatusVisible); [14-08-20]

      menus.menuBar.remove(horizontalGlue);
      menus.menuBar.remove(btFullScreen);
      
      if(prevIsMaximized) {
        setExtendedState(MAXIMIZED_BOTH);
      }

      setVisible(true);
      isFullScreen = false;
    }
    viewer.focusOnActiveBuffer();
  }
  
  /**
   * 
   */
  public void reduceAsTrayIcon() {
    if (!SystemTray.isSupported())
      return;

    saveAll();

    try {
      setVisible(false);

      int w = SystemTray.getSystemTray().getTrayIconSize().width;
      Image image = (w == 16) ?
          GUIUtilities.getIcon("icon-jnpad16.png").getImage() : //$NON-NLS-1$
          IconUtilities.createImage(new IconUtilities.LetterIcon("j", true, w, w, true)); //$NON-NLS-1$

      final TrayIcon tic = new TrayIcon(image, getTitle());
      SystemTray.getSystemTray().add(tic);
      tic.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(final MouseEvent e) {
          setVisible(true);
          SystemTray.getSystemTray().remove(tic);
        }
      });
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Sets the tool bars.
   */
  public void setToolBars() {
    toolbars.setToolBars();
  }

  /**
   *
   * @param b boolean
   */
  public void viewToolBarsVisible(boolean b) {
    toolbars.viewToolBarsVisible(b);
  }

  /**
   *
   * @param b boolean
   */
  public void setStatusBarVisible(boolean b) {
    statusBar.setVisible(b);
    JNPadAction.checkSelection(actions.statusBarVisibleAction, b);
  }

  /**
   *
   * @return boolean
   */
  public boolean isStatusBarVisible() {
    return statusBar.isVisible();
  }

  /**
   *
   * @param b boolean
   */
  public void setToolBarVisible(boolean b) {
    toolbars.setToolBarVisible(b);
  }

  /**
   *
   * @return boolean
   */
  public boolean isToolBarVisible() {
    return toolbars.isToolBarVisible();
  }

  /**
   *
   * @param b boolean
   */
  public void setFindResultsVisible(boolean b) {
    if (b != isFindResultsVisible()) {
      if (b) {
        if (sppOutputPosition < 0) 
          sppOutputPosition = pnBackground.getHeight() * 2 / 3;
        pnBackground.remove(viewer);
        pnBackground.add(sppOutput, BorderLayout.CENTER);
        sppOutput.add(viewer, JSplitPane.TOP);
        sppOutput.setDividerLocation(sppOutputPosition);
        findResultsPanel.requestFocusInWindow();
      }
      else {
        sppOutputPosition = sppOutput.getDividerLocation();
        pnBackground.remove(sppOutput);
        pnBackground.add(viewer, BorderLayout.CENTER);
        viewer.focusOnActiveBuffer();
      }
      pnBackground.revalidate(); // pnBackground.validate();
      JNPadAction.checkSelection(actions.findResultsVisibleAction, b);
      isFindResultsVisible = b;
    }
  }

  /**
   *
   * @return boolean
   */
  public boolean isFindResultsVisible() {
    return isFindResultsVisible;
  }

  /**
   *
   * @param text String
   * @param path String
   */
  public void addNewFound(String text, String path) {
    findResultsPanel.addNewFound(text, path);
  }

  /**
   *
   * @param text Text
   */
  public void addNewText(Text text) {
    findResultsPanel.addNewText(text);
  }

  /**
   *
   */
  public void showFoundTarget() {
    findResultsPanel.showFoundTarget();
  }

  /**
   *
   * @param b boolean
   */
  public void setIncrementalSearchVisible(boolean b) {
    if (b != isIncrementalSearchVisible()) {
      if (b) {
        if (incrementalSearchPanel == null) {
          incrementalSearchPanel = new IncrementalSearchPanel(this);
        }
        pnBackground.add(incrementalSearchPanel, BorderLayout.SOUTH);
        statusBar.setBorder(new EdgeBorder(EdgeBorder.EDGE_TOP));
        incrementalSearchPanel.requestFocusInWindow();
      }
      else if (incrementalSearchPanel != null) {
        pnBackground.remove(incrementalSearchPanel);
        statusBar.setBorder(null);
        viewer.focusOnActiveBuffer();
      }
      isIncrementalSearchVisible = b;
      pnBackground.validate();
    }
  }

  /**
   *
   * @return boolean
   */
  public boolean isIncrementalSearchVisible() {
    return isIncrementalSearchVisible;
  }

  /**
   *
   */
  public void showFindDialog() {
    if(findDialog == null) {
      findDialog = new FindDialog(this);
      actions.findNextAction.setEnabled(true);
      actions.findPreviousAction.setEnabled(true);
    }
    else {
      findDialog.setVisible(true);
    }
  }

  /**
   * Find next.
   */
  public void findNext() {
    if (findDialog != null) {
      findDialog.findNext();
    }
  }

  /**
   * Find previous.
   */
  public void findPrevious() {
    if (findDialog != null) {
      findDialog.findPrevious();
    }
  }

  /**
   * Show replace dialog.
   */
  public void showReplaceDialog() {
    if(replaceDialog == null) {
      replaceDialog = new ReplaceDialog(this);
      actions.replaceNextAction.setEnabled(true);
    }
    else {
      replaceDialog.setVisible(true);
    }
  }

  /**
   * Replace next.
   */
  public void replaceNext() {
    if (replaceDialog != null) {
      replaceDialog.replaceNext();
    }
  }
  
  /**
   * Gets the viewer.
   *
   * @return the viewer
   */
  public Viewer getViewer() {
    return viewer;
  }

  /**
   * Gets the active buffer set.
   *
   * @return the active buffer set
   */
  public BufferSet getActiveBufferSet() {
    try {
      return viewer.getActiveBufferSet();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return null;
    }
  }

  /**
   * Gets the active buffer.
   *
   * @return the active buffer
   */
  public Buffer getActiveBuffer() {
    try {
      return viewer.getActiveBuffer();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return null;
    }
  }

  /**
   * Gets the active edit pane.
   *
   * @return the active edit pane
   */
  public EditPane getActiveEditPane() {
    try {
      Buffer activeBuffer = getActiveBuffer();
      if (activeBuffer != null)
        return activeBuffer.getSelectedEditPane();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
    return null;
  }
  
  /**
   * Gets the active text area.
   *
   * @return EditTextArea
   */
  public JNPadTextArea getActiveTextArea() {
    try {
      Buffer activeBuffer = getActiveBuffer();
      if (activeBuffer != null)
        return activeBuffer.getSelectedTextArea();
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
    return null;
  }

  /**
   * Page layout.
   */
  public void pageLayout() {
    PrinterJob pj = PrinterJob.getPrinterJob();
    // show the page format dialog, allow user changes to format
    pageFormat = pj.pageDialog(pageFormat);
    // make sure the pageformat is ok
    pageFormat = pj.validatePage(pageFormat);
  }

  /**
   * 
   * @return PageFormat
   */
  public PageFormat getPageFormat() {
    return pageFormat;
  }

  /**
   * Show wait cursor.
   */
  public void showWaitCursor() {
    if (waitCount++ == 0) {
      setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
    }
  }

  /**
   * Hide wait cursor.
   */
  public void hideWaitCursor() {
    if (waitCount > 0) {
      waitCount--;
    }
    if (waitCount == 0) {
      setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
    }
  }

  /**
   * Reset.
   */
  void reset() {
    if (!isResetEnabled)
      return;
    index_file = 0;
    doNewFile(false);
  }
  
  /**
   * Handle state changed.
   *
   * @param buffer the buffer
   */
  void handleStateChanged(Buffer buffer) {
    lastDirty = null;
    lastReadOnly = null;
    setStatus(buffer.getFilePath(), (Config.isDistractionFreeMode() || isFullScreen()) ? TIMEOUT_NONE : TIMEOUT_DEFAULT);
    updateControls(buffer);
  }

  /**
   * Check init file.
   *
   * @return true, if successful
   */
  private boolean checkInitFile() {
    if (viewer.getBufferCount() == 1) {
      Buffer buffer = viewer.getBufferAt(0);
      return buffer.isNew() && !buffer.isDirty();
    }
    return false;
  }

  /**
   * Sets the save session enabled.
   */
  private void setSaveSessionEnabled() {
    boolean b = false;
    for (int i = 0; !b && i < viewer.getBufferCount(); i++) {
      Buffer buffer = viewer.getBufferAt(i);
      b = !buffer.isNew();
    }
    actions.saveSessionAction.setEnabled(b);
  }
  
  /**
   * Sets the save all enabled.
   */
  private void setSaveAllEnabled() {
    boolean b = false;
    for (int i = 0; !b && i < viewer.getBufferCount(); i++) {
      Buffer buffer = viewer.getBufferAt(i);
      b = buffer.isDirty() && !buffer.isReadOnly();
    }
    actions.saveAllFilesAction.setEnabled(b);
  }

  /**
   *
   */
  private void setCloseOtherEnabled() {
    boolean b = viewer.getBufferCount() > 1;
    actions.closeOtherAction.setEnabled(b);
    actions.closeAllToLeftAction.setEnabled(b);
    actions.closeAllToRightAction.setEnabled(b);
  }

  /**
   * Update controls.
   *
   * @see jnpad.config.Updatable#updateControls()
   */
  @Override
  public void updateControls() {
    updateControls(CTRLS_ALL);
  }

  /**
   * Update controls.
   *
   * @param ctrls the ctrls
   * @see jnpad.config.Updatable#updateControls(int)
   */
  @Override
  public void updateControls(final int ctrls) {
    try {
      updateControls(getActiveBuffer(), ctrls);
    }
    catch (Exception ex) { // no debería pasar
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
    }
  }

  /**
   * Update controls.
   *
   * @param buffer the buffer
   */
  public void updateControls(Buffer buffer) {
    updateControls(buffer, CTRLS_ALL);
  }

  /**
   * Update controls.
   *
   * @param buffer the buffer
   * @param ctrls the ctrls
   */
  public void updateControls(Buffer buffer, final int ctrls) {
    viewer.updateControls(ctrls);
    buffer.updateControls(ctrls);

    if ( (ctrls & CTRLS_EDITABLE) != 0) {
      updateEditableControls(buffer);
    }

    if ( (ctrls & CTRLS_TEXT_CHANGED) != 0) {
      updateTextChangedControls(buffer);
    }

    if ( (ctrls & CTRLS_EOL) != 0) {
      statusBar.setEOL(buffer.getLineSeparator());
    }

    if ( (ctrls & CTRLS_ENCODING) != 0) {
      statusBar.setEncoding(buffer.getEncoding());
    }

    if ( (ctrls & CTRLS_TEXT_MODE) != 0) {
      statusBar.setAsOverwriteTextMode(buffer.getSelectedTextArea().isOverwriteTextMode());
    }

    if ( (ctrls & CTRLS_VIEW) != 0) {
      updateViewControls(buffer);
    }
  }

  /**
   * Update text changed controls.
   *
   * @param buffer the buffer
   */
  private void updateTextChangedControls(Buffer buffer) {
    final boolean isReadOnly = buffer.isReadOnly();
    final boolean isDirty    = buffer.isDirty();
    
    if (lastDirty == null || lastReadOnly == null || 
        lastDirty != isDirty || lastReadOnly != isReadOnly) {

      int i = viewer.indexOf(buffer);
      if (i >= 0 && i < viewer.getBufferCount()) {
        viewer.setComponentIconAt(i);
      }

      actions.saveFileAction.setEnabled(!isReadOnly && isDirty);
      setSaveAllEnabled();
      setSaveSessionEnabled();
      
      StringBuilder sb = new StringBuilder();
      sb.append(JNPad.TITLE);
      sb.append(isDirty ? " - * " : " - "); //$NON-NLS-1$ //$NON-NLS-2$
      sb.append(buffer.getFilePath());
      if(isReadOnly)
        sb.append(" [").append(JNPadBundle.getString("JNPad.readOnly")).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
      setTitle(sb.toString());

      lastReadOnly = isReadOnly;
      lastDirty = isDirty;
    }
  }

  /**
   * Update editable controls.
   *
   * @param buffer the buffer
   */
  private void updateEditableControls(Buffer buffer) {
    final boolean isEditable = !buffer.isReadOnly();

    if(isEditable != lastGrobalEditable) {
      ActionManager.INSTANCE.setEnabledBy(isEditable, buffer.hasSelection(), buffer.canUndo(), buffer.canRedo(), buffer.isDirty());

      updateTextChangedControls(buffer);

      GUIUtilities.setButtonGroupEnabled(menus.bgEOL, isEditable);
      GUIUtilities.setButtonGroupEnabled(menus.bgEncoding, isEditable);

      lastGrobalEditable = isEditable;

      JNPadAction.setGlobalItemStateChangedEnabled(false);
      actions.setReadOnlyAction.setSelected(!isEditable);
      JNPadAction.setGlobalItemStateChangedEnabled(true);
      statusBar.setReadOnlyIndicatorEnabled(!isEditable);
    }
  }

  /**
   * Update view controls.
   *
   * @param buffer the buffer
   */
  private void updateViewControls(Buffer buffer) {
    JNPadAction.setGlobalItemStateChangedEnabled(false);
    actions.lineWrapAction.setSelected(buffer.getLineWrap());
    actions.lineNumbersAction.setSelected(buffer.isLineNumbersVisible());
    actions.activeLineAction.setSelected(buffer.isActiveLineVisible());
    actions.rightMarginLineAction.setSelected(buffer.isRightMarginLineVisible());
    actions.markOccurrencesAction.setSelected(buffer.isOccurrencesHighlighterVisible());
    actions.markStripAction.setSelected(buffer.isMarkStripVisible());
    actions.setReadOnlyAction.setSelected(buffer.isReadOnly());
    JNPadAction.setGlobalItemStateChangedEnabled(true);

    statusBar.setReadOnlyIndicatorEnabled(buffer.isReadOnly());
    
    updateEOLControls(buffer);
    updateEncodingControls(buffer);
  }

  /**
   * Update eol controls.
   *
   * @param buffer the buffer
   */
  private void updateEOLControls(Buffer buffer) {
    JNPadAction.setGlobalItemStateChangedEnabled(false);
    LineSeparator lineSeparator = buffer.getLineSeparator();
    if (lineSeparator == LineSeparator.DOS)
      GUIUtilities.setSelectedButton(menus.bgEOL, 0);
    else if (lineSeparator == LineSeparator.UNIX)
      GUIUtilities.setSelectedButton(menus.bgEOL, 1);
    else if (lineSeparator == LineSeparator.MAC)
      GUIUtilities.setSelectedButton(menus.bgEOL, 2);
    JNPadAction.setGlobalItemStateChangedEnabled(true);

    statusBar.setEOL(buffer.getLineSeparator());
  }

  /**
   * Update encoding controls.
   *
   * @param buffer the buffer
   */
  private void updateEncodingControls(Buffer buffer) {
    JNPadAction.setGlobalItemStateChangedEnabled(false);
    String encoding = buffer.getEncoding();
    for (Enumeration<AbstractButton> e = menus.bgEncoding.getElements(); e.hasMoreElements();) {
      AbstractButton b = e.nextElement();
      if (b.getText().equals(encoding)) {
        b.setSelected(true);
        break;
      }
    }
    JNPadAction.setGlobalItemStateChangedEnabled(true);

    statusBar.setEncoding(buffer.getEncoding());
  }

  /**
   * Configure.
   *
   * @param cfg the cfg
   * @see jnpad.config.Configurable#configure(int)
   */
  @Override
  public void configure(final int cfg) {
    viewer.configure(cfg);
    findResultsPanel.configure(cfg);
    ((Configurable) actions.fileBrowserAction).configure(cfg);
    ((Configurable) actions.executeActionAction).configure(cfg);
  }

  /**
   *
   * @return JPopupMenu
   */
  JPopupMenu createTabbedPopupMenu() {
    return menus.createTabbedPopupMenu();
  }

  /**
   *
   * @return JPopupMenu
   */
  JPopupMenu createEncodingPopupMenu() {
    return menus.createEncodingPopupMenu();
  }

  /**
   * New file.
   *
   * @return true, if successful
   */
  public boolean newFile() {
    return doNewFile(true);
  }
 
  /**
   * Do new file.
   *
   * @param status the status
   * @return true, if successful
   */
  private boolean doNewFile(boolean status) {
    String name = JNPadBundle.getString("newFile.new", (++index_file)).concat(".txt"); //$NON-NLS-1$ //$NON-NLS-2$
    try {
      Buffer buffer = new Buffer(viewer.getActiveBufferSet(), name, EMPTY_STRING);

      // cargar el archivo al editor
      viewer.add(buffer);
      viewer.setActiveIndex(viewer.getBufferCount() - 1);

      // los archivos nuevos nacen sucios
      //buffer.setDirty(true);

      buffer.addPropertyChangeListener(propertyChangeHandler);

      if (status) { // muestra el nombre del archivo abierto en la barra de estado
        setStatus(JNPadBundle.getString("newFile.open", name), TIMEOUT_DEFAULT); //$NON-NLS-1$
      }

      // actualizar los menus
      updateControls(buffer);

      buffer.requestFocus();
      setCloseOtherEnabled();
      return true;
    }
    catch (Exception ex) {
      String msg = JNPadBundle.getString("newFile.error"); //$NON-NLS-1$
      setStatus(StatusType.ERROR, msg);
      LOGGER.log(Level.WARNING, msg, ex);
      return false;
    }
  }

  /**
   * Open file.
   *
   * @return true, if successful
   */
  public boolean openFile() {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setMultiSelectionEnabled(true);
    fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    fileChooser.setAcceptAllFileFilterUsed(true);
    fileChooser.setCurrentDirectory(new File(Config.CHOOSER_FILE_DIRECTORY.getValue()));

    // Utilizar la versión OPEN del cuadro de diálogo, comprobar el valor
    // devuelto de Aceptar/Cancelar
    if (JFileChooser.APPROVE_OPTION != fileChooser.showOpenDialog(super.rootPane))
      return false;

    File[] files = fileChooser.getSelectedFiles();
    if (files.length > 0) {
      boolean b = true;
      Config.CHOOSER_FILE_DIRECTORY.setValue(files[0].getParent());
      for (File file : files) {
        b = b && openFile(file.getAbsolutePath());
      }
      return b;
    }
    return false;
  }

  /**
   * Open file.
   *
   * @param in the in
   * @return true, if successful
   */
  public boolean openFile(JNPadInput in) {
    return openFile(in.getPath(), in.getCaretPosition(), in.isReadOnly(), in.getSplitConfig(), in.isLineWrapped());
  }

  /**
   * Open file as read only.
   *
   * @param filePath the file path
   * @return true, if successful
   */
  public boolean openFileAsReadOnly(String filePath) {
    return openFile(filePath, -1, true, EMPTY_STRING, false);
  }
  
  /**
   * Open file.
   *
   * @param filePath the file path
   * @return true, if successful
   */
  public boolean openFile(String filePath) {
    return openFile(filePath, -1, false, EMPTY_STRING, false);
  }

  /**
   * Open file.
   *
   * @param filePath the file path
   * @param caretPosition the caret position
   * @param isReadOnly is read only
   * @param isLineWrapped is line wrapped
   * @return true, if successful
   */
  public boolean openFile(String filePath, int caretPosition, boolean isReadOnly, boolean isLineWrapped) {
    return openFile(filePath, caretPosition, isReadOnly, EMPTY_STRING, isLineWrapped);
  }

  /**
   * Open file.
   *
   * @param filePath the file path
   * @param caretPosition the caret position
   * @param isReadOnly is read only
   * @param splitConfig the split config
   * @param isLineWrapped is line wrapped
   * @return true, if successful
   */
  public boolean openFile(String filePath, int caretPosition, boolean isReadOnly, String splitConfig, boolean isLineWrapped) {
    if(checkInitFile()) {
      isResetEnabled = false;
      doCloseFile(null, false);
      isResetEnabled = true;
    }

    String path;
    if (filePath == null) {
      JFileChooser fileChooser = new JFileChooser();
      fileChooser.setMultiSelectionEnabled(true);
      fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
      fileChooser.setAcceptAllFileFilterUsed(false);
      fileChooser.setCurrentDirectory(new File(Config.CHOOSER_FILE_DIRECTORY.getValue()));

      // Utilizar la versión OPEN del cuadro de diálogo, comprobar el valor
      // devuelto de Aceptar/Cancelar
      if (JFileChooser.APPROVE_OPTION != fileChooser.showOpenDialog(super.rootPane))
        return false;

      File file = fileChooser.getSelectedFile();
      path = file.getAbsolutePath();
      caretPosition = -1;
      Config.CHOOSER_FILE_DIRECTORY.setValue(file.getParent());
    }
    else {
      path = filePath;
    }

    String name = Utilities.getFileName(path);

    int index;
    if ( (index = viewer.indexOf(new MockBuffer(path))) != -1) {
      Buffer buffer = viewer.getBufferAt(index);
      if (buffer.isDirty()) { // el archivo abierto fue modificado
        String[] options = { JNPadBundle.getYesOptionText(), JNPadBundle.getCancelOptionText() };
        int option = JOptionPane.showOptionDialog(this,
                                                  JNPadBundle.getString("openFile.warning.message", path), //$NON-NLS-1$
                                                  JNPadBundle.getString("openFile.warning.title", name), //$NON-NLS-1$
                                                  JOptionPane.DEFAULT_OPTION,
                                                  JOptionPane.ERROR_MESSAGE,
                                                  null,
                                                  options,
                                                  options[0]);
        // si se elige "Cancelar" o se cierra la ventana sin hacer una
        // elección sale
        if (option == 1 || option == JOptionPane.CLOSED_OPTION)
          return false;
        // si se elige "Si" se sobreescribe el archivo que fue modificado
        // por el abierto del mismo nombre
        // (sobreescribir = cerrar modificado y abrir nuevo)
        if (!closeFile(path, false))
          return false;
      }
      else { // el archivo abierto no fue modificado
        setStatus(JNPadBundle.getString("openFile.warning.message2", path), TIMEOUT_DEFAULT); //$NON-NLS-1$
        updateControls(viewer.getBufferAt(index));
        viewer.setActiveIndex(index);
      }
    }
    
    try {
      // si se abre un archivo que no está abierto
      if (viewer.indexOf(new MockBuffer(path)) == -1) {
        BufferedInputStream in = new BufferedInputStream(new FileInputStream(path));

        // detectar encoding
        String encoding = GUIUtilities.detectEncoding(in);
        if(encoding == null || !Charset.isSupported(encoding)) {
          encoding = Config.FILE_ENCODING.getValue();
        }

        // crea una array de bytes del tamaño del archivo,
        // para utiliarla como buffer de datos,en el que leer
        // los datos del archivo
        byte[] buff = new byte[in.available()];

        // leer todos los bytes disponibles en el buffer
        in.read(buff, 0, buff.length);
        in.close();

        // encoding
        String s;
        try {
          s = new String(buff, 0, buff.length, encoding);
        }
        catch (Exception ex) { // no debería pasar
          LOGGER.fine("Unsupported Encoding " + encoding); //$NON-NLS-1$
          s = new String(buff, 0, buff.length, Config.FILE_ENCODING.getDefault());
          Config.FILE_ENCODING.setToDefault();
        }

        GUIUtilities.StringConversion conversion = GUIUtilities.getStringConversion(s, Config.REMOVE_END_SPACES.getValue());

        Buffer buffer = new Buffer(viewer.getActiveBufferSet(), path, conversion.getResult());
        buffer.setLineSeparator(conversion.getLineSeparator(), false);
        buffer.setReadOnly(isReadOnly);
        buffer.setEncoding(encoding, false);

        // set caret position
        try {
          if (caretPosition < 0)
            caretPosition = getRecentFileCursorPosition(path);
          buffer.getSelectedTextArea().setCaretPosition(caretPosition);
        }
        catch(Exception ex) {
          buffer.getSelectedTextArea().setCaretPosition(0);
        }

        // cargar el archivo al editor
        viewer.add(buffer);
        viewer.setActiveIndex(viewer.getBufferCount() - 1);
        
        buffer.setInitialSplitConfig(splitConfig);

        buffer.getSelectedEditPane().setLineWrap(isLineWrapped); // [v0.3]

        buffer.addPropertyChangeListener(propertyChangeHandler);

        // muestra el nombre del archivo abierto en la barra de estado
        setStatus(JNPadBundle.getString("openFile.open", path), TIMEOUT_DEFAULT); //$NON-NLS-1$
        // actualizar los menus
        updateControls(buffer);

        buffer.requestFocus();
      }
      setCloseOtherEnabled();
      return true;
    }
    catch (Exception ex) {
      String msg = JNPadBundle.getString("openFile.error", path); //$NON-NLS-1$
      setStatus(StatusType.ERROR, msg);
      LOGGER.log(Level.WARNING, msg, ex);
      if (viewer.getBufferCount() == 0) {
        reset();
      }
      return false;
    }
  }

  /**
   * Open files.
   *
   * @param inputs the inputs
   * @return true, if successful
   */
  public boolean openFiles(List<JNPadInput> inputs) {
    if (inputs.size() > 0) {
      int openedIndex = -1;
      for (int i = 0; i < inputs.size(); i++) {
        JNPadInput in = inputs.get(i);
        if (in.isCurrent()) {
          openedIndex = i;
        }
        openFile(in);
      }
      if (openedIndex >= 0 && openedIndex < viewer.getBufferCount()) {
        viewer.setActiveIndex(openedIndex);
      }
      updateControls();
      return true;
    }
    return false;
  }
  
  /**
   * Cierrar archivo.
   *
   * @param filePath String
   * @param confirmation boolean si es <code>true</code> cierra con confirmaciï¿½n de
   * que hay archivos modificados que no fueron guardados, si es
   * <code>false</code> cierra sin hacer confirmaciï¿½n
   * @return boolean
   */
  public boolean closeFile(String filePath, boolean confirmation) {
    return !checkInitFile() && doCloseFile(filePath, confirmation);
  }

  /**
   *
   * @param filePath String
   * @param confirmation boolean
   * @return boolean
   */
  private boolean doCloseFile(String filePath, boolean confirmation) {
    Buffer buffer = null;
    int index;
    if (filePath == null) {
      index = viewer.getActiveIndex();
      buffer = viewer.getActiveBuffer();
    }
    else {
      index = viewer.indexOf(new MockBuffer(filePath));
      if (index != -1)
        buffer = viewer.getBufferAt(index);
    }

    // si no se asigna archivo a cerrar sale hacer nada
    if (buffer == null)
      return false;

    // si el archivo tiene modificaciones sin guardar
    if (confirmation && buffer.isDirty()) {
      String[] options = { JNPadBundle.getYesOptionText(), JNPadBundle.getNoOptionText(), JNPadBundle.getCancelOptionText() };
      int option = JOptionPane.showOptionDialog(this,
                                                JNPadBundle.getString("closeFile.warning.message", buffer.getFilePath()), //$NON-NLS-1$
                                                JNPadBundle.getString("closeFile.warning.title"), //$NON-NLS-1$
                                                JOptionPane.DEFAULT_OPTION,
                                                JOptionPane.ERROR_MESSAGE,
                                                null,
                                                options,
                                                options[0]);
      // si se elige "Cancelar" o se cierra la ventana sin haber elegido
      // sale sin hacer nada
      if (option == 2 || option == JOptionPane.CLOSED_OPTION)
        return false;

      // si se elige "Si" guarda el archivo
      if (option == 0)
        saveFile(filePath);
    }

    buffer.removePropertyChangeListener(propertyChangeHandler);

    // Cerrar:
    setStatus(JNPadBundle.getString("closeFile.close", buffer.getFilePath()), TIMEOUT_DEFAULT); //$NON-NLS-1$

    menus.updateRecentFiles(buffer.getFilePath(), 
                            buffer.getCaretPosition(), 
                            buffer.isReadOnly(),
                            buffer.isSplitted() ? buffer.getSplitConfig() : EMPTY_STRING,
                            buffer.isLineWrapped());

    viewer.removeBufferAt(index);
    if (viewer.getBufferCount() > 0) {
      int j = index;
      if (index >= viewer.getBufferCount())
        j = viewer.getBufferCount() - 1;
      viewer.setActiveIndex(j);
      updateControls(viewer.getActiveBuffer());
    }

    setSaveAllEnabled();
    setCloseOtherEnabled();

    viewer.focusOnActiveBuffer();

    Utilities.gc_(); // [added v0.3]

    return true;
  }

  /**
   * Close other files.
   */
  public void closeOtherFiles() {
    int i = viewer.getActiveIndex();
    int count = viewer.getBufferCount();
    for (int j = count - 1; j >= 0; j--) {
      if (j != i) {
        Buffer buffer = viewer.getBufferAt(j);
        doCloseFile(buffer.getFilePath(), true);
      }
    }
  }

  /**
   * Close all to right.
   */
  public void closeAllToRight() {
    int i = viewer.getActiveIndex();
    int count = viewer.getBufferCount();
    for (int j = count - 1; j > i; j--) {
      Buffer buffer = viewer.getBufferAt(j);
      doCloseFile(buffer.getFilePath(), true);
    }
  }

  /**
   * Close all to left.
   */
  public void closeAllToLeft() {
    int i = viewer.getActiveIndex();
    for (int j = 0; j < i; j++) {
      Buffer buffer = viewer.getBufferAt(0);
      doCloseFile(buffer.getFilePath(), true);
    }
  }

  /**
   * Close all.
   */
  public void closeAll() {
    int count = viewer.getBufferCount();
    for (int j = count - 1; j >= 0; j--) {
      Buffer buffer = viewer.getBufferAt(j);
      closeFile(buffer.getFilePath(), true);
    }
  }

  /**
   * Close files.
   */
  public void closeFiles() {
    ExitDialog.showDialog(this, viewer.getActiveBufferSet(), ExitDialog.CLOSE);
  }

  /**
   * Save file.
   *
   * @param filePath the file path
   * @return true, if successful
   */
  public boolean saveFile(String filePath) {
    try {
      Buffer buffer;
      int index;

      if (filePath != null) {
        index = viewer.indexOf(new MockBuffer(filePath));
        buffer = viewer.getBufferAt(index);
      }
      else {
        buffer = viewer.getActiveBuffer();
        index = viewer.getActiveIndex();
      }

      if (buffer == null)
        return false;

      filePath = buffer.getFilePath();

      //Gestionar donde aun no exista nombre de archivo
      File f = new File(filePath);
      if(!f.exists()) {
        return saveFileAs(buffer, index);
      }

      // si se pide archivo de seguridad (.ext~)
      if (Config.SECURE_SAVE.getValue()) {
        String name = Utilities.getFileName(filePath);
        name = name.concat(String.valueOf('~'));
        try {
          BufferedInputStream in = new BufferedInputStream(new FileInputStream(filePath));
          byte[] data = new byte[in.available()];
          in.read(data, 0, data.length);
          in.close();
          String dirPath = filePath.substring(0, filePath.lastIndexOf(Utilities.DIR_SEPARATOR) + 1);

          BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(dirPath.concat(name)));
          out.write(data);
          out.close();
        }
        catch (IOException ex) {
          //ignored
        }
      }

      BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(filePath));
      out.write(getData(buffer));
      out.close();

      buffer.setAsSaved();

      updateControls(buffer);

      setStatus(JNPadBundle.getString("saveFile.save", filePath), TIMEOUT_DEFAULT); //$NON-NLS-1$
    }
    catch (Exception ex) {
      String msg = JNPadBundle.getString("saveFile.error", filePath); //$NON-NLS-1$
      setStatus(StatusType.ERROR, msg);
      LOGGER.log(Level.WARNING, msg, ex);
      return false;
    }
    return true;
  }

  /**
   * Save file as.
   *
   * @param buffer the buffer
   * @param index the index
   * @return true, if successful
   */
  private boolean saveFileAs(Buffer buffer, int index) {
    String path = buffer.getFilePath();

    JFileChooser fileChooser = new JFileChooser();
    File file= new File(path);
    if (file.exists()) {
      fileChooser.setSelectedFile(file);
    }
    else {
      fileChooser.setCurrentDirectory(new File(Config.CHOOSER_FILE_DIRECTORY.getValue()));
    }
    fileChooser.rescanCurrentDirectory();

    // Utilizar la version SAVE del cuadro de dialogo, comprobar el valor
    // devuelto de Aceptar/Cancelar
    if (JFileChooser.APPROVE_OPTION == fileChooser.showSaveDialog(super.rootPane)) {
      File selectedFile = fileChooser.getSelectedFile();
      path = selectedFile.getAbsolutePath();
      String name = Utilities.getFileName(path);
      
      if (selectedFile.exists()) {
        String[] options = { JNPadBundle.getYesOptionText(), JNPadBundle.getNoOptionText() };
        int option = JOptionPane.showOptionDialog(this,
                                                  JNPadBundle.getString("saveFile.warning.message", path), //$NON-NLS-1$
                                                  JNPadBundle.getString("saveFile.warning.title", name), //$NON-NLS-1$
                                                  JOptionPane.DEFAULT_OPTION,
                                                  JOptionPane.ERROR_MESSAGE,
                                                  null,
                                                  options,
                                                  options[0]);
        if (option == 1 || option == JOptionPane.CLOSED_OPTION)
          return false;
      }
      Config.CHOOSER_FILE_DIRECTORY.setValue(selectedFile.getParent());

      try {
        if (index == -1)
          index = viewer.getActiveIndex();

        BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(path));
        out.write(getData(buffer));
        out.close();
        buffer.setFilePath(path);

        buffer.setAsSaved();

        setStatus(JNPadBundle.getString("saveFile.save", path), TIMEOUT_DEFAULT); //$NON-NLS-1$

        viewer.setTitleAt(index, Utilities.getFileName(path));
        viewer.setToolTipTextAt(index, path);
        viewer.setComponentIconAt(index);

        updateControls(buffer);
      }
      catch (Exception ex) {
        String msg = JNPadBundle.getString("saveFile.error", path); //$NON-NLS-1$
        setStatus(StatusType.ERROR, msg);
        LOGGER.log(Level.WARNING, msg, ex);
        return false;
      }
    }
    return true;
  }

  /**
   *
   * @return boolean
   */
  public boolean saveFileAs() {
    return saveFileAs(viewer.getActiveBuffer(), -1);
  }

  /**
   * Save all.
   */
  public void saveAll() {
    int i = 0;
    for (int j = 0; j < viewer.getBufferCount(); j++) {
      Buffer buffer = viewer.getBufferAt(j);
      if (buffer.isDirty()) {
        saveFile(buffer.getFilePath());
        i++;
      }
    }
    setSaveAllEnabled();
    if (i > 1)
      setStatus(statusBar.getStatus().concat(JNPadBundle.getString("saveAll.save", i)), TIMEOUT_DEFAULT); //$NON-NLS-1$
  }
  
  /**
   * Gets the data.
   *
   * @param buffer the buffer
   * @return the data
   * @throws UnsupportedEncodingException the unsupported encoding exception
   */
  private static byte[] getData(final Buffer buffer) throws UnsupportedEncodingException {
    String encoding = buffer.getEncoding();
    byte[] data;
    try {
      data = GUIUtilities.formatString(buffer.getText(), buffer.getLineSeparator().getLineSeparator()).getBytes(encoding);
    }
    catch (Exception ex) {
      LOGGER.fine("Unsupported Encoding " + encoding); //$NON-NLS-1$
      try {
        encoding = Config.FILE_ENCODING.getValue();
        data = GUIUtilities.formatString(buffer.getText(), buffer.getLineSeparator().getLineSeparator()).getBytes(encoding);
      }
      catch(Exception ex2) {
        LOGGER.fine("Unsupported Encoding " + encoding); //$NON-NLS-1$
        encoding = Config.FILE_ENCODING.getDefault();
        data = GUIUtilities.formatString(buffer.getText(), buffer.getLineSeparator().getLineSeparator()).getBytes(encoding);
      }
      buffer.setEncoding(encoding, false);
    }
    return data;
  }

  /**
   * Load session.
   *
   * @return true, if successful
   */
  public boolean loadSession() {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setDialogTitle(JNPadBundle.getString("loadSession.title")); //$NON-NLS-1$
    fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    fileChooser.setAcceptAllFileFilterUsed(false);
    fileChooser.addChoosableFileFilter(new JNPadFileFilter());
    fileChooser.setCurrentDirectory(new File(Config.CHOOSER_FILE_DIRECTORY.getValue()));

    // Utilizar la versión OPEN del cuadro de diálogo, comprobar el valor
    // devuelto de Aceptar/Cancelar
    if (JFileChooser.APPROVE_OPTION != fileChooser.showOpenDialog(super.rootPane))
      return false;

    File file = fileChooser.getSelectedFile();
    String path = file.getAbsolutePath();
    Config.CHOOSER_FILE_DIRECTORY.setValue(file.getParent());
    
    JNPadParser parser = new JNPadParser(path);
    try {
      parser.load();
    }
    catch(Exception ex) {
      setStatus(StatusType.ERROR, ex.getMessage());
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return false;
    }

    return openFiles(parser.getJNPadInputList());
  }

  /**
   * Save session.
   *
   * @return true, if successful
   */
  public boolean saveSession() {
    JFileChooser fileChooser = new JFileChooser();
    fileChooser.setDialogTitle(JNPadBundle.getString("saveSession.title")); //$NON-NLS-1$
    fileChooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
    fileChooser.setAcceptAllFileFilterUsed(false);
    fileChooser.addChoosableFileFilter(new JNPadFileFilter());
    fileChooser.setCurrentDirectory(new File(Config.CHOOSER_FILE_DIRECTORY.getValue()));

    // Utilizar la versión OPEN del cuadro de diálogo, comprobar el valor
    // devuelto de Aceptar/Cancelar
    if (JFileChooser.APPROVE_OPTION != fileChooser.showSaveDialog(super.rootPane))
      return false;

    File file = fileChooser.getSelectedFile();
    String path = file.getAbsolutePath();
    String name = path.substring(path.lastIndexOf(Utilities.DIR_SEPARATOR) + 1);
    String dir = path.substring(0, path.lastIndexOf(Utilities.DIR_SEPARATOR) + 1);

    if (!name.toLowerCase().endsWith(".jnpad")) { //$NON-NLS-1$
      name = name.concat(".jnpad"); //$NON-NLS-1$
      path = dir.concat(name);
    }

    Config.CHOOSER_FILE_DIRECTORY.setValue(file.getParent());

    file = new File(path);
    if (file.exists()) {
      String[] options = { JNPadBundle.getYesOptionText(), JNPadBundle.getCancelOptionText() };
      int option = JOptionPane.showOptionDialog(this,
                                                JNPadBundle.getString("saveFile.warning.message", path), //$NON-NLS-1$
                                                JNPadBundle.getString("saveFile.warning.title", name), //$NON-NLS-1$
                                                JOptionPane.DEFAULT_OPTION,
                                                JOptionPane.ERROR_MESSAGE,
                                                null,
                                                options,
                                                options[0]);
      if (option == 1 || option == JOptionPane.CLOSED_OPTION)
        return false;
    }
    
    final int selected_index = viewer.getActiveIndex();

    List<JNPadInput> list = new ArrayList<JNPadInput>(viewer.getBufferCount());

    for (int i = 0; i < viewer.getBufferCount(); i++) {
      Buffer buffer = viewer.getBufferAt(i);
      String path_ = buffer.getFilePath();
      File file_ = new File(path_);
      if (!file_.isFile()) {
        continue;
      }
      list.add(new JNPadInput(path_, 
                              buffer.getCaretPosition(), 
                              buffer.isReadOnly(), 
                              selected_index == i, 
                              buffer.isSplitted() ? buffer.getSplitConfig() : EMPTY_STRING,
                              buffer.isLineWrapped()));
    }

    if(list.size() > 0) {
      JNPadParser parser = new JNPadParser(path);
      parser.addAll(list);

      try {
        parser.save();
      }
      catch (Exception ex) {
        setStatus(StatusType.ERROR, ex.getMessage());
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
        return false;
      }
    }

    return true;
  }

  /**
   * Open all recent files.
   */
  public void openAllRecentFiles() {
    menus.openAllRecentFiles();
  }

  /**
   * Empty recent files list.
   */
  public void emptyRecentFilesList() {
    menus.emptyRecentFilesList();
  }

  /**
   *
   * @param s String
   * @return int
   */
  private int getRecentFileCursorPosition(String s) {
    List<JNPadInput> l = Config.getJNPadInputListProp("files.recent"); //$NON-NLS-1$
    for (JNPadInput in : l) {
      if (Utilities.equals(in.getPath(), s, !Platform.isWindows)) {
        return in.getCaretPosition();
      }
    }
    return 0;
  }

  /**
   * Load opened files.
   *
   * @return true, if successful
   */
  private boolean loadOpenedFiles() {
    return openFiles(Config.getJNPadInputListProp("files.opened")); //$NON-NLS-1$
  }

  /**
   * Save opened files.
   */
  private void saveOpenedFiles() {
    final int selected_index = viewer.getActiveIndex();
    
    List<JNPadInput> list = new ArrayList<JNPadInput>(viewer.getBufferCount());

    for (int i = 0; i < viewer.getBufferCount(); i++) {
      Buffer buffer = viewer.getBufferAt(i);
      String path = buffer.getFilePath();
      File file = new File(path);
      if (!file.isFile()) {
        continue;
      }
      
      list.add(new JNPadInput(path, 
                              buffer.getCaretPosition(), 
                              buffer.isReadOnly(), 
                              selected_index == i, 
                              buffer.isSplitted() ? buffer.getSplitConfig() : EMPTY_STRING,
                              buffer.isLineWrapped()));
    }

    if(list.size() > 0) {
      Config.setJNPadInputListProp("files.opened", list); //$NON-NLS-1$
    }
    else {
      Config.remove("files.opened"); //$NON-NLS-1$
    }
  }

  /**
   * Reload.
   */
  public void reload() {
    reload(getActiveBuffer());
  }

  /**
   * Reload.
   *
   * @param buffer the buffer
   */
  public void reload(Buffer buffer) {
    if (buffer == null) {
      return;
    }

    try {
      buffer.reload();

      // muestra el nombre del archivo abierto en la barra de estado
      setStatus(JNPadBundle.getString("openFile.open", buffer.getFilePath()), TIMEOUT_DEFAULT); //$NON-NLS-1$
      // actualizar los menus
      updateControls(buffer);

      buffer.requestFocus();
    }
    catch (Exception ex) {
      String msg = JNPadBundle.getString("reload.error", buffer.getFilePath()); //$NON-NLS-1$
      setStatus(StatusType.ERROR, msg);
      LOGGER.log(Level.WARNING, msg, ex);
    }
  }

  /**
   * Clear status.
   *
   * @see jnpad.ui.status.StatusDisplayable#clearStatus()
   */
  @Override
  public void clearStatus() {
    statusBar.clearStatus();
  }

  /**
   * Sets the status.
   *
   * @param text the new status
   * @see jnpad.ui.status.StatusDisplayable#setStatus(java.lang.String)
   */
  @Override
  public void setStatus(String text) {
    statusBar.setStatus(text);
  }

  /**
   * Sets the status.
   *
   * @param text String
   * @param timeout_sec int
   */
  @Override
  public void setStatus(String text, int timeout_sec) {
    statusBar.setStatus(text, timeout_sec);
  }

  /**
   * Sets the status.
   *
   * @param type StatusType
   * @param text String
   * @see jnpad.ui.status.StatusDisplayable#setStatus(jnpad.ui.status.StatusDisplayable.StatusType,
   * java.lang.String)
   */
  @Override
  public void setStatus(final StatusType type, final String text) {
    statusBar.setStatus(type, text);
  }

  /**
   * Sets the status.
   *
   * @param type StatusType
   * @param text String
   * @param timeout_sec int
   * @see jnpad.ui.status.StatusDisplayable#setStatus(jnpad.ui.status.StatusDisplayable.StatusType,
   * java.lang.String, int)
   */
  @Override
  public void setStatus(final StatusType type, final String text, final int timeout_sec) {
    statusBar.setStatus(type, text, timeout_sec);
  }

  /**
   * Gets the status.
   *
   * @return the status
   * @see jnpad.ui.status.StatusDisplayable#getStatus()
   */
  @Override
  public String getStatus() {
    return statusBar.getStatus();
  }
  
  /**
   * Gets the status bar.
   *
   * @return the status bar
   */
  public IStatusBar getStatusBar() {
    return statusBar;
  }

  /**
   * Select searched text.
   *
   * @param text the text
   * @param comp the comp
   */
  public void selectSearchedText(Text text, Component comp)  {
    MockBuffer mock = new MockBuffer(text.getPath());

    int index = viewer.indexOf(mock);

    if (index == -1) {
      openFile(text.getPath());
      if (comp != null)
        comp.requestFocus();
    }

    index = viewer.indexOf(mock);
    if (index != -1) {
      Buffer buffer = viewer.getBufferAt(index);
      viewer.setActiveBuffer(buffer);
      if (comp != null)
        comp.requestFocus();
      try {
        buffer.getSelectedEditPane().highlightSearch(text.getContext(), text.getStartPosition(), text.getEndPosition());
      }
      catch (Exception ex) {
        LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      }
    }
  }

  /**
   * Save settings.
   */
  private void saveSettings() {
    boolean isMaximized = getExtendedState() == MAXIMIZED_BOTH;

    if(!isMaximized) {
      Config.JNPAD_X.setValue(getX());
      Config.JNPAD_Y.setValue(getY());
      Config.JNPAD_WIDTH.setValue(getWidth());
      Config.JNPAD_HEIGHT.setValue(getHeight());
    }

    Config.JNPAD_MAXIMIZE.setValue(isMaximized);
    Config.STATUSBAR_VISIBLE.setValue(isStatusBarVisible());     // [added v0.2]
    Config.FINDRESULTS_VISIBLE.setValue(isFindResultsVisible()); // [added v0.2]
    Config.TOOLBAR_VISIBLE.setValue(isToolBarVisible());         // [added v0.3]

    saveOpenedFiles();
  }

  /**
   * Exit.
   */
  public void exit() {
    if (ExitDialog.showDialog(this, viewer.getActiveBufferSet(), ExitDialog.SAVE)) {
      saveSettings();
      Config.saveProperties();
      Utilities.exitSuccess();
    }
  }

  /**
   * Restart.
   * @since 0.3
   */
  public void restart() {
    if (ExitDialog.showDialog(this, viewer.getActiveBufferSet(), ExitDialog.SAVE)) {
      saveSettings();
      Config.saveProperties();
      try {
        Utilities.restartApplication(null);
      }
      catch (IOException e) {
        try {
          Utilities.restartApplication2("jnpad-0.3.jar"); //$NON-NLS-1$
        }
        catch (IOException ex) {
          LOGGER.log(Level.WARNING, ex.getMessage(), ex);
        }
      }
    }
  }
  
  /**
   *
   * @param e WindowEvent
   */
  @Override
  protected void processWindowEvent(WindowEvent e) {
    if (e.getID() == WindowEvent.WINDOW_CLOSING) {
      exit();
      return;
    }
    else if (e.getID() == WindowEvent.WINDOW_OPENED) {
      if (Config.FINDRESULTS_VISIBLE.getValue()) // [added 160118]
        setFindResultsVisible(true);
      setInitialSplitConfiguration();
      Buffer activeBuffer = viewer.getActiveBuffer();
      if (activeBuffer != null) 
        GUIUtilities.requestFocus(activeBuffer);
    }
    super.processWindowEvent(e);
  }

  /**
   * Sets the initial split configuration.
   */
  private void setInitialSplitConfiguration() {
    try {
      boolean b = false;
      for (Buffer eview : viewer.getBuffers()) {
        String isc = eview.getInitialSplitConfig();
        if (Utilities.isNotEmptyString(isc)) {
          eview.setSplitConfig(isc);
          b = true;
        }
      }
      if (b) {
        updateControls(CTRLS_SPLITTING);
      }
    }
    catch (Exception ex) {
      if (LOGGER.isLoggable(Level.FINEST))
        LOGGER.log(Level.FINEST, ex.getMessage(), ex);
    }
  }
  
  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class PropertyChangeHandler.
   */
  private class PropertyChangeHandler implements PropertyChangeListener, Serializable {
    /** UID */
    private static final long serialVersionUID = 7695232497147556601L;

    /**
     * Property change.
     *
     * @param e PropertyChangeEvent
     */
    @Override 
    public void propertyChange(final PropertyChangeEvent e) {
      String propName = e.getPropertyName();
      Object obj = e.getSource();

      Buffer activeBuffer = getActiveBuffer();
      
      //System.out.println("--- " + (activeBuffer == obj));
      
      // dejar pasar los eventos que pertenecen a la vista activa
      if (activeBuffer == null || obj != activeBuffer) {
        return;
      }

      if (Buffer.PROPERTY_DIRTY.equals(propName)) {
        updateTextChangedControls(activeBuffer);
      }
      else if (Buffer.PROPERTY_READ_ONLY.equals(propName)) {
        updateEditableControls(activeBuffer);
      }
      else if (Buffer.PROPERTY_LINE_SEPARATOR.equals(propName)) {
        updateEOLControls(activeBuffer);
      }
      else if (Buffer.PROPERTY_ENCODING.equals(propName)) {
        reload(activeBuffer);
        //setStatusMessageEncoding(buffer.getEncoding()); // updateEncodingControls(buffer);
      }
    }
  }
  //////////////////////////////////////////////////////////////////////////////
  
}
