/*
 * 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 java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Frame;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Image;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.FilteredImageSource;
import java.awt.image.RGBImageFilter;
import java.io.BufferedInputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.EventObject;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.swing.AbstractButton;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JPopupMenu;
import javax.swing.JWindow;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.border.Border;

import jnpad.config.Config;
import jnpad.ui.MnemonicHelper;
import jnpad.ui.Orientable;
import jnpad.ui.icon.EmptyIcon;
import jnpad.ui.plaf.LAFUtils;
import jnpad.util.LineSeparator;
import jnpad.util.Platform;
import jnpad.util.Utilities;

import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;

/**
 * The Class GUIUtilities.
 *
 * @version 0.3
 * @since   jNPad v0.1
 */
public final class GUIUtilities {
  /** The Constant EMPTY_ICON. */
  public static final Icon                   EMPTY_ICON          = EmptyIcon.ICON_16;

  private static final Insets                NULL_INSETS         = new Insets(0, 0, 0, 0);

  private static final int                   MAX_ITEM_LIST_ELEM  = 40, MIN_ITEM_LIST_ELEM = 4;

  private static Map<Integer, GradientPaint> m_gpCache;

  private static RGBImageFilter              imageIconFilter;

  @SuppressWarnings("nls")
  private static final String[]              FONT_STYLES         = { "Plain", "Bold", "Italic", "Bold Italic" };
  @SuppressWarnings("nls")
  private static final String[]              HIDE_FONTS          = { ".bold", ".italic" };
  @SuppressWarnings("nls")
  private static final String[]              STANDARD_FONT_SIZES = { "8", "9", "10", "11", "12", "14", "16", "18", "20", "22", "24", "26", "28", "36", "48", "72" };

  @SuppressWarnings("all")
  public enum FontSize {TINY, SMALL, NORMAL, LARGE, HUGE}
  @SuppressWarnings("all")
  public enum ComponentStyle {REGULAR, SMALL, MINI}
  @SuppressWarnings("all")
  public enum ColorTone {DARKER, NORMAL, BRIGHTER}
  
  /** Logger */
  private static final Logger                LOGGER              = Logger.getLogger(GUIUtilities.class.getName());

  /** no instances */
  private GUIUtilities() {
    super();
  }

  /**
   * Gets the selected button index.
   *
   * @param group the group
   * @return the selected button index
   */
  public static int getSelectedButtonIndex(ButtonGroup group) {
    Enumeration<AbstractButton> enumeration = group.getElements();
    int i = 0;
    while (enumeration.hasMoreElements()) {
      AbstractButton button = enumeration.nextElement();
      if (group.isSelected(button.getModel())) {
        return i;
      }
      i++;
    }
    return -1;
  }

  /**
   * Gets the selected button.
   *
   * @param group the group
   * @return the selected button
   */
  public static AbstractButton getSelectedButton(ButtonGroup group) {
    Enumeration<AbstractButton> enumeration = group.getElements();
    while (enumeration.hasMoreElements()) {
      AbstractButton button = enumeration.nextElement();
      if (group.isSelected(button.getModel())) {
        return button;
      }
    }
    return null;
  }

  /**
   * Sets the selected button.
   *
   * @param group the group
   * @param index the index
   */
  public static void setSelectedButton(ButtonGroup group, int index) {
    Enumeration<AbstractButton> enumeration = group.getElements();
    int i = 0;
    while (enumeration.hasMoreElements()) {
      AbstractButton button = enumeration.nextElement();
      group.setSelected(button.getModel(), index == i);
      i++;
    }
  }

  /**
   * Sets the button group enabled.
   *
   * @param group the group
   * @param enabled the enabled
   */
  public static void setButtonGroupEnabled(ButtonGroup group, boolean enabled) {
    Enumeration<AbstractButton> enumeration = group.getElements();
    while (enumeration.hasMoreElements()) {
      AbstractButton button = enumeration.nextElement();
      button.setEnabled(enabled);
    }
  }
  
  /**
   * Creates the menu.
   *
   * @param text the text
   * @param icon the icon
   * @return the menu
   */
  public static JMenu createMenu(String text, Icon icon) {
    JMenu m = new JMenu();
    GUIUtilities.setLocalizedText(m, text);
    if (icon != null && Config.isDefaultMode()) {
      m.setIcon(icon);
    }
    return m;
  }
  
  /**
   * Gets the jNPad's frame.
   *
   * @param evt the event object
   * @return the jNPad's frame
   */
  public static JNPadFrame getJNPadFrame(EventObject evt) {
    if (evt != null) {
      Object o = evt.getSource();
      if (o instanceof Component) {
        return getJNPadFrame( (Component) o);
      }
    }
    return null;
  }

  /**
   * Gets the jNPad's frame.
   *
   * @param comp the component
   * @return the jNPad's frame
   */
  public static JNPadFrame getJNPadFrame(Component comp) {
    for (; ; ) {
      if (comp instanceof JNPadFrame) {
        return (JNPadFrame) comp;
      }
      else if (comp instanceof JPopupMenu) {
        comp = ( (JPopupMenu) comp).getInvoker();
      }
      else if (comp != null) {
        comp = comp.getParent();
      }
      else {
        break;
      }
    }
    return null;
  }
  
  /**
   * Sets the text for a menu item or other subclass of AbstractButton.
   * <p>Examples:</p>
   * <table cellspacing="2" cellpadding="3" border="1">
   *   <tr><th>Input String</th>                                   <th>View under JDK 1.4 or later</th></tr>
   *   <tr><td><code>Save &amp;As<code></td>                       <td>Save <u>A</u>s</td></tr>
   *   <tr><td><code>Rock &amp; Roll<code></td>                    <td>Rock &amp; Roll</td></tr>
   *   <tr><td><code>Drag &amp; &amp;Drop<code></td>               <td>Drag &amp; <u>D</u>rop</td></tr>
   *   <tr><td><code>&amp;&#1060;&#1072;&#1081;&#1083;</code></td> <td><u>&#1060;</u>&#1072;&#1081;&#1083;</td></tr>
   * </table>
   *
   * @param item a button whose text will be changed
   * @param text new label
   */
  public static void setLocalizedText(AbstractButton item, String text) {
    setLocalizedText2(item, text);
  }

  /**
   * Sets the text for the label or other subclass of JLabel.
   * For details see {@link #setLocalizedText(AbstractButton, String)}.
   *
   * @param item a label whose text will be set
   * @param text new label
   */
  public static void setLocalizedText(JLabel item, String text) {
    setLocalizedText2(item, text);
  }

  /**
   * Actual setter of the text & mnemonics for the AbstractButton/JLabel or
   * their subclasses.
   *
   * @param item AbstractButton/JLabel
   * @param text new label
   */
  private static void setLocalizedText2(Object item, String text) {
    if (text == null || item == null) {
      return;
    }

    int i = findMnemonicAmpersand(text);

    if (i < 0) {
      // no '&' - don't set the mnemonic
      setText(item, text);
      setMnemonic(item, 0);
    }
    else {
      setText(item, text.substring(0, i) + text.substring(i + 1));
      //#67807 no mnemonics on macosx
      if (Platform.isMac) {
        setMnemonic(item, 0);
      }
      else {
        char ch = text.charAt(i + 1);
        if (text.startsWith("<html>")) { //$NON-NLS-1$
          // Workaround for JDK bug #6510775
          setText(item, text.substring(0, i) + "<u>" + ch + "</u>" + text.substring(i + 2));  //$NON-NLS-1$//$NON-NLS-2$
          i += 3; // just in case it gets fixed
        }
        if ( ( (ch >= 'A') && (ch <= 'Z')) || ( (ch >= 'a') && (ch <= 'z')) || ( (ch >= '0') && (ch <= '9'))) {
          // it's latin character or arabic digit,
          // setting it as mnemonics
          int vk = ch;
          if (vk >= 'a' && vk <= 'z')
            vk -= ('a' - 'A');
          setMnemonic(item, vk);
        }
        else {
          // it's non-latin, getting the latin correspondance
          try {
            int latinCode = getLatinKeycode(ch);
            setMnemonic(item, latinCode);
          }
          catch (MissingResourceException ex) {
            //ignored
          }
        }
      }
    }
  }

  /**
   * Sets the mnemonic.
   *
   * @param menu the menu
   * @param mnemonicHelper the mnemonic helper
   * @param submenus the submenus
   */
  public static void setMnemonic(JMenu menu, MnemonicHelper mnemonicHelper, boolean submenus) {
    int len = menu.getMenuComponentCount();

    for (int i = 0; i < len; i++) {
      Component c = menu.getMenuComponent(i);
      if (c instanceof AbstractButton) {
        if (mnemonicHelper != null) {
          char mnemonic = mnemonicHelper.getMnemonic(((AbstractButton) c).getText());
          if (mnemonic != 0)
            ((AbstractButton) c).setMnemonic(mnemonic);
        }
        else {
          ((AbstractButton) c).setMnemonic('\0');
        }
      }
    }

    if (submenus) {
      if (mnemonicHelper != null) {
        mnemonicHelper.clear();
      }
      for (int i = 0; i < len; i++) {
        Component c = menu.getMenuComponent(i);
        if (c instanceof JMenu) {
          setMnemonic((JMenu) c, mnemonicHelper, true);
        }
      }
    }
  }
  
  /**
   * Sets the text.
   *
   * @param item Object
   * @param text String
   */
  private static void setText(Object item, String text) {
    if (item instanceof AbstractButton) {
      ( (AbstractButton) item).setText(text);
    }
    else {
      ( (JLabel) item).setText(text);
    }
  }

  /**
   * Sets the mnemonic.
   *
   * @param item Object
   * @param mnem int
   */
  private static void setMnemonic(Object item, int mnem) {
    if (Platform.isMac) {
      // there shall be no mnemonics on macosx.
      //#55864
      return;
    }
    if (item instanceof AbstractButton) {
      ((AbstractButton) item).setMnemonic(mnem);
    }
    else {
      ((JLabel) item).setDisplayedMnemonic(mnem);
    }
  }

  /**
   * Searches for an ampersand in a string which indicates a mnemonic.
   * Recognizes the following cases:
   * <ul>
   * <li>"Drag & Drop", "Ampersand ('&')" - don't have mnemonic ampersand.
   *      "&" is not found before " " (space), or if enclosed in "'"
   *     (single quotation marks).
   * <li>"&File", "Save &As..." - do have mnemonic ampersand.
   * <li>"Rock & Ro&ll", "Underline the '&' &character" - also do have
   *      mnemonic ampersand, but the second one.
   * <li>"&lt;html&gt;&lt;b&gt;R&amp;amp;D&lt;/b&gt; departmen&amp;t" - has mnemonic
   *      ampersand before "t".
   *      Ampersands in HTML texts that are part of entity are ignored.
   * </ul>
   * @param text text to search
   * @return the position of mnemonic ampersand in text, or -1 if there is none
   */
  public static int findMnemonicAmpersand(String text) {
    int i = -1;
    boolean isHTML = text.startsWith("<html>"); //$NON-NLS-1$

    do {
      // searching for the next ampersand
      i = text.indexOf('&', i + 1);

      if ( (i >= 0) && ( (i + 1) < text.length())) {
        if (isHTML) {
          boolean startsEntity = false;
          for (int j = i + 1; j < text.length(); j++) {
            char c = text.charAt(j);
            if (c == ';') {
              startsEntity = true;
              break;
            }
            if (!Character.isLetterOrDigit(c)) {
              break;
            }
          }
          if (!startsEntity) {
            return i;
          }
        }
        else {
          // before ' '
          if (text.charAt(i + 1) == Utilities.SPACE) {
            continue;

            // before ', and after '
          }
          else if ( (text.charAt(i + 1) == '\'') && (i > 0) && (text.charAt(i - 1) == '\'')) {
            continue;
          }

          // ampersand is marking mnemonics
          return i;
        }
      }
    } while (i >= 0);

    return -1;
  }

  /**
   * Gets the Latin symbol which corresponds
   * to some non-Latin symbol on the localized keyboard.
   * The search is done via lookup of Resource bundle
   * for pairs having the form (e.g.) <code>MNEMONIC_\u0424=A</code>.
   *
   * @param localeChar non-Latin character or a punctuator to be used as mnemonic
   * @return character on latin keyboard, corresponding to the locale character,
   * or the appropriate VK_*** code (if there's no latin character
   * "under" the non-Latin one
   * 
   * @throws MissingResourceException the missing resource exception
   */
  public static int getLatinKeycode(char localeChar) throws MissingResourceException {
    // associated should be a latin character, arabic digit
    // or an integer (KeyEvent.VK_***)
    String str = getMnemonicsBundle().getString("MNEMONIC_" + localeChar); // NOI18N //$NON-NLS-1$

    if (str.length() == 1) {
      return str.charAt(0);
    }
    return Integer.parseInt(str);
  }

  /**
   * Gets the mnemonics bundle.
   *
   * @return the mnemonics bundle
   */
  private static ResourceBundle getMnemonicsBundle() {
    return ResourceBundle.getBundle("jnpad.i18n.MnemonicsBundle"); //$NON-NLS-1$
  }

  /**
   * Gets the icon.
   *
   * @param picture String
   * @param source Class
   * @return ImageIcon
   * @throws FileNotFoundException the file not found exception
   */
  public static ImageIcon getIcon(String picture, Class<?> source) throws FileNotFoundException {
    return getIcon(picture, source, true);
  }

  /**
   * Gets the icon.
   *
   * @param picture the picture
   * @param source the source
   * @param filter the filter
   * @return the icon
   * @throws FileNotFoundException the file not found exception
   */
  public static ImageIcon getIcon(String picture, Class<?> source, boolean filter) throws FileNotFoundException {
    try {
      ImageIcon icon = new ImageIcon(source.getResource("icons/".concat(picture))); //$NON-NLS-1$
      if (filter && LAFUtils.isDarkLAF()) {
        RGBImageFilter imageFilter = getImageIconFilter();
        if (null != imageFilter) {
          Image image = Toolkit.getDefaultToolkit().createImage(new FilteredImageSource(icon.getImage().getSource(), imageFilter));
          icon = new ImageIcon(image);
        }
      }
      return icon;
    }
    catch (Exception e) {
      throw new FileNotFoundException("No se encontró el archivo: icons/".concat(picture)); //$NON-NLS-1$
    }
  }

  /**
   * Gets the image icon filter.
   *
   * @return the image icon filter
   */
  private static RGBImageFilter getImageIconFilter() {
    if (imageIconFilter == null) {
      Object obj = UIManager.get("jnpad.imageicon.filter"); //$NON-NLS-1$
      if (obj instanceof RGBImageFilter) {
        imageIconFilter = (RGBImageFilter) obj;
      }
    }
    return imageIconFilter;
  }
  
  /**
   * Gets the icon.
   *
   * @param picture String
   * @return ImageIcon
   * @throws FileNotFoundException the file not found exception
   */
  public static ImageIcon getIcon(String picture) throws FileNotFoundException {
    return getIcon(picture, GUIUtilities.class);
  }

  /**
   * Gets the icon.
   *
   * @param picture the picture
   * @param filter the filter
   * @return the icon
   * @throws FileNotFoundException the file not found exception
   */
  public static ImageIcon getIcon(String picture, boolean filter) throws FileNotFoundException {
    return getIcon(picture, GUIUtilities.class, filter);
  }
  
  /**
   * Load icon.
   *
   * @param picture the picture
   * @param source the source
   * @return the image icon
   */
  public static ImageIcon loadIcon(String picture, Class<?> source) {
    try {
      return getIcon(picture, source);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return null;
    }
  }

  /**
   * Load icon.
   *
   * @param picture the picture
   * @param source the source
   * @param filter the filter
   * @return the image icon
   */
  public static ImageIcon loadIcon(String picture, Class<?> source, boolean filter) {
    try {
      return getIcon(picture, source, filter);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return null;
    }
  }
  
  /**
   *
   * @param picture String
   * @return ImageIcon
   */
  public static ImageIcon loadIcon(String picture) {
    try {
      return getIcon(picture);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return null;
    }
  }

  /**
   * Load icon.
   *
   * @param picture the picture
   * @param filter the filter
   * @return the image icon
   */
  public static ImageIcon loadIcon(String picture, boolean filter) {
    try {
      return getIcon(picture, filter);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, ex.getMessage(), ex);
      return null;
    }
  }
  
  /**
   * Creates the empty border.
   *
   * @param gap int
   * @return Border
   */
  public static Border createEmptyBorder(int gap) {
    return BorderFactory.createEmptyBorder(gap, gap, gap, gap);
  }

  /**
   * Creates the empty border.
   *
   * @param hgap int
   * @param vgap int
   * @return Border
   */
  public static Border createEmptyBorder(int hgap, int vgap) {
    return BorderFactory.createEmptyBorder(vgap, hgap, vgap, hgap);
  }
  
  /**
   * Gets the string conversion.
   *
   * @param str String
   * @param removeEndSpaces boolean
   * @return StringConversion
   */
  public static GUIUtilities.StringConversion getStringConversion(String str, boolean removeEndSpaces) {
    GUIUtilities.StringConversion sc = new GUIUtilities.StringConversion(str, removeEndSpaces);
    sc.convert();
    return sc;
  }

  /**
   * Detect eol.
   *
   * @param str String
   * @return StringConversion
   */
  public static GUIUtilities.StringConversion detectEOL(String str) {
    GUIUtilities.StringConversion sc = new GUIUtilities.StringConversion(str, false);
    sc.detectEOL();
    return sc;
  }
  
  /**
   * Convert string.
   *
   * @param str String
   * @param removeEndSpaces boolean
   * @param tabSize int
   * @return String
   */
  public static String convertString(String str, boolean removeEndSpaces, int tabSize) {
    GUIUtilities.StringConversion sc = new GUIUtilities.StringConversion(str, removeEndSpaces, tabSize);
    sc.convertText();
    return sc.getResult();
  }

  /**
   * Format string.
   *
   * @param text String
   * @param lineSeparator String
   * @return String
   */
  public static String formatString(String text, String lineSeparator) {
    if (lineSeparator == null)
      lineSeparator = Utilities.LINE_SEPARATOR; // el de la plataforma
    return Utilities.convertLineEndings(text, lineSeparator);
  }

  /**
   * Detect encoding.
   *
   * @param in InputStream
   * @return String
   * @throws IOException Signals that an I/O exception has occurred.
   */
  public static String detectEncoding(InputStream in) throws IOException {
    CharsetDetector cd = new CharsetDetector();
    cd.setText(in);
    CharsetMatch cm = cd.detect();
    return cm == null ? null : cm.getName();
  }

  /**
   * Gets the detectable encodings.
   *
   * @return the detectable encodings
   */
  public static String[] getDetectableEncodings() {
    return CharsetDetector.getAllDetectableCharsets();
  }

  /**
   * Gets the jNPad default encoding.
   *
   * @return the jNPad default encoding
   */
  public static String getJNPadDefaultEncoding() {
    String encoding = null;

    try {
      File f = new File("jnpad-temp"); //$NON-NLS-1$

      PrintWriter pw = null;
      try {
        pw = new PrintWriter(new BufferedWriter(new FileWriter(f)));
        pw.println("# Este es un archivo de prueba"); //$NON-NLS-1$
      }
      catch (Exception ex) {
        encoding = getDefaultEncoding();
      }
      finally {
        if (pw != null) {
          try {
            pw.close();
          }
          catch (Exception ex) {
            //ignored
          }
        }
      }

      if (encoding == null) {
        BufferedInputStream in = null;
        try {
          in = new BufferedInputStream(new FileInputStream(f));
          encoding = detectEncoding(in);
        }
        catch (Exception ex) {
          encoding = getDefaultEncoding();
        }
        finally {
          if (in != null) {
            try {
              in.close();
            }
            catch (Exception ex) {
              //ignored
            }
          }
        }
      }

      @SuppressWarnings("unused")
      boolean bool = f.delete(); //Keep FindBugs happy
    }
    catch (Exception ex) {
      encoding = getDefaultEncoding();
    }

    return encoding;
  }

  /**
   * Gets the default encoding.
   *
   * @return the default encoding
   */
  public static String getDefaultEncoding() {
    String encoding = Charset.defaultCharset().name();
    if (encoding == null) {
      try {
        File f = File.createTempFile("jnpad-temp", null); //$NON-NLS-1$
        FileWriter w = new FileWriter(f);
        encoding = w.getEncoding();
        w.close();
        f.deleteOnExit(); //delete(); Keep FindBugs happy
      }
      catch (IOException ioe) {
        encoding = System.getProperty("file.encoding", "UTF-8"); //$NON-NLS-1$//$NON-NLS-2$
      }
    }
    return encoding;
  }

  /**
   * Gets the charset tree set.
   *
   * @return the charset tree set
   */
  public static TreeSet<String> getCharsetTreeSet() {
    TreeSet<String> result = new TreeSet<String>();
    result.add(Config.FILE_ENCODING.getValue());
    for (String encoding : getDetectableEncodings()) {
      if (Charset.isSupported(encoding)) {
        result.add(encoding);
      }
    }
    return result;
  }

  /**
   * Beep.
   */
  public static void beep() {
    Toolkit.getDefaultToolkit().beep();
  }

  /**
   * Adds the close with escape key.
   *
   * @param comp Window
   * @throws IllegalArgumentException the illegal argument exception
   */
  public static void addCloseWithEscapeKey(final Window comp) throws IllegalArgumentException {
    Component cont;
    if (comp instanceof JFrame) {
      cont = ((JFrame) comp).getContentPane();
    }
    else if (comp instanceof JDialog) {
      cont = ((JDialog) comp).getContentPane();
    }
    else if (comp instanceof JWindow) {
      cont = ((JWindow) comp).getContentPane();
    }
    else {
      throw new IllegalArgumentException(JNPadBundle.getString("utils.error.0") + comp.getClass()); //$NON-NLS-1$
    }

    ((JComponent) cont).registerKeyboardAction(new ActionListener() {
      public void actionPerformed(final ActionEvent e) {
        comp.setVisible(false);
        comp.dispose();
      }
    }, "Escape", KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT); //$NON-NLS-1$
  }

  /**
   * Run or invoke and wait.
   *
   * @param r the runnable
   * @throws InvocationTargetException the invocation target exception
   * @throws InterruptedException the interrupted exception
   */
  public static void runOrInvokeAndWait(Runnable r) throws InvocationTargetException, InterruptedException {
    if (SwingUtilities.isEventDispatchThread())
      r.run();
    else
      SwingUtilities.invokeAndWait(r);
  }

  /**
   * Run or invoke later.
   *
   * @param r the runnable
   */
  public static void runOrInvokeLater(Runnable r) {
    if (SwingUtilities.isEventDispatchThread())
      r.run();
    else
      SwingUtilities.invokeLater(r);
  }

  /**
   * Request focus.
   *
   * @param c the component
   */
  public static void requestFocus(final Component c) {
    if (c == null)
      return;
    if (c.isShowing())
      c.requestFocus();
    else {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          c.requestFocus();
        }
      });
    }
  }

  /**
   * Request focus in window.
   *
   * @param c the component
   */
  public static void requestFocusInWindow(final Component c) {
    if (c == null)
      return;
    if (c.isShowing())
      c.requestFocusInWindow();
    else {
      SwingUtilities.invokeLater(new Runnable() {
        public void run() {
          c.requestFocusInWindow();
        }
      });
    }
  }

  /**
   * Focuses on the specified component as soon as the window becomes active.
   * 
   * @param win The window
   * @param comp The component
   */
  public static void requestFocus(final Window win, final Component comp) {
    win.addWindowListener(new WindowAdapter() {
      @Override
      public void windowActivated(WindowEvent e) {
        comp.requestFocus();
        win.removeWindowListener(this);
      }
    });
  }

  /**
   * Focuses on the specified component as soon as the window becomes active.
   * 
   * @param win The window
   * @param comp The component
   */
  public static void requestFocusInWindow(final Window win, final Component comp) {
    win.addWindowFocusListener(new WindowAdapter() {
      @Override
      public void windowGainedFocus(WindowEvent evt) {
        SwingUtilities.invokeLater(new Runnable() {
          public void run() {
            comp.requestFocusInWindow();
          }
        });
        win.removeWindowFocusListener(this);
      }
    });
  }
  
  /**
   * Check has focus.
   *
   * @param container the container
   * @return true, if successful
   */
  public static boolean checkHasFocus(Container container) {
    Component[] components = container.getComponents();
    for (Component component : components) {
      if ((component instanceof Container) && (checkHasFocus((Container) component)))
        return true;
      if (component.hasFocus())
        return true;
    }
    return false;
  }

  /**
   * Gets the focused component.
   *
   * @return the focused component
   */
  public static Component getFocusedComponent() {
    Frame frame = getFocusedFrame();
    if (frame == null)
      return null;
    return frame.getFocusOwner();
  }

  /**
   * Gets the focused frame.
   *
   * @return the focused frame
   */
  public static Frame getFocusedFrame() {
    Frame[] frames = Frame.getFrames();
    for (Frame frame : frames) {
      if ((frame.isVisible()) && (frame.getFocusOwner() != null))
        return frame;
    }
    return null;
  }

  /**
   * Checks if is ancestor of focus owner.
   *
   * @param component the component
   * @return true, if is ancestor of focus owner
   */
  public static boolean isAncestorOfFocusOwner(Component component) {
    boolean hasFocus = false;
    Component focusOwner = KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner();
    if (component == focusOwner || (component instanceof Container && ( (Container) component).isAncestorOf(focusOwner))) {
      hasFocus = true;
    }
    return hasFocus;
  }
  
  /**
   * Update size.
   *
   * @param label the label
   * @param widestString the widest string
   */
  public static void updateSize(JLabel label, String widestString) {
    Dimension maxDimension = label.getPreferredSize();
    Font f = label.getFont();
    if (f != null) {
      Border b = label.getBorder();
      Insets ins = (b != null) ? b.getBorderInsets(label) : NULL_INSETS;
      FontMetrics fm = label.getFontMetrics(f);
      int mw = fm.stringWidth(label.getText());
      maxDimension.height = fm.getHeight() + ins.top + ins.bottom;
      if (widestString != null) {
        mw = Math.max(mw, fm.stringWidth(widestString));
      }
      maxDimension.width = mw + ins.left + ins.right;
    }
    label.setPreferredSize(maxDimension);
    label.setMinimumSize(maxDimension);
  }

  /**
   * Sets the relative size.
   *
   * @param components the components
   */
  public static void setRelativeSize(JComponent... components) {
    Dimension dimension = new Dimension(0, 0);
    for (JComponent component : components)
      if (component.getPreferredSize().width > dimension.width)
        dimension = component.getPreferredSize();
    for (JComponent component : components)
      component.setPreferredSize(dimension);
  }

  /**
   * Gets the file path.
   *
   * @param fm FontMetrics
   * @param path String
   * @param width int
   * @return String
   */
  public static String getFilePath(FontMetrics fm, String path, int width) {
    int w = fm.stringWidth(path);
    StringBuilder sb = new StringBuilder(path);
    boolean b = false;
    for (; w > width; w = fm.stringWidth(sb.toString())) {
      int idx1 = sb.toString().indexOf(Utilities.DIR_SEPARATOR);
      int idx2 = sb.toString().indexOf(Utilities.DIR_SEPARATOR, idx1 + 1);
      if (idx2 < 0)
        idx2 = sb.toString().length();
      try {
        sb.delete(idx1, idx2);
        b = true;
        continue;
      }
      catch (Exception ex) {
        idx1 = sb.toString().indexOf('/');
      }
      idx2 = sb.toString().indexOf('/', idx1 + 1);
      if (idx1 == -1 || idx2 == -1) {
        idx1 = idx2 = 0;
        if (sb.length() > 0)
          idx2 = 1;
      }
      sb.delete(idx1, idx2);
      b = true;
    }

    if (b) {
      try {
        sb.insert(sb.toString().indexOf(Utilities.DIR_SEPARATOR),
                            Utilities.DIR_SEPARATOR + "..."); //$NON-NLS-1$
      }
      catch (Exception ex) {
        try {
          sb.insert(sb.toString().indexOf('/'), "/..."); //$NON-NLS-1$
        }
        catch (Exception exc) {
          //ignored
        }
      }
    }
    return sb.toString();
  }

  /**
   * Gets the screen dimension.
   *
   * @return the screen dimension
   */
  public static Dimension getScreenDimension() {
    return Toolkit.getDefaultToolkit().getScreenSize();
  }

  /**
   * Center component.
   *
   * @param comp the comp
   */
  public static void centerComponent(Component comp) {
    Dimension sSize = getScreenDimension();
    Dimension cSize = comp.getSize();
    if (cSize.height > sSize.height)
      cSize.height = sSize.height;
    if (cSize.width > sSize.width)
      cSize.width = sSize.width;

    comp.setLocation(new Point( (sSize.width - cSize.width) / 2,
                               (sSize.height - cSize.height) / 2));
  }

  /**
   * Gets the maximum window bounds.
   *
   * @return the maximum window bounds
   */
  public static Rectangle getMaximumWindowBounds() {
    final GraphicsEnvironment lge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    try {
      return lge.getMaximumWindowBounds();
    }
    catch (Exception ex) {
      final Dimension s = getScreenDimension();
      return new Rectangle(0, 0, s.width, s.height);
    }
  }

  /**
   * Gets the screen bounds for point.
   *
   * @param p the point
   * @return the screen bounds for point
   */
  public static Rectangle getScreenBoundsForPoint(Point p) {
    return getScreenBoundsForPoint(p.x, p.y);
  }

  /**
   * Gets the screen bounds for point.
   *
   * @param x the x
   * @param y the y
   * @return the screen bounds for point
   */
  public static Rectangle getScreenBoundsForPoint(int x, int y) {
    final GraphicsEnvironment lge = GraphicsEnvironment.getLocalGraphicsEnvironment();
    GraphicsDevice[] devices = lge.getScreenDevices();
    for (GraphicsDevice device : devices) {
      GraphicsConfiguration config = device.getDefaultConfiguration();
      Rectangle gcBounds = config.getBounds();
      if (gcBounds.contains(x, y)) {
        return gcBounds;
      }
    }
    return getMaximumWindowBounds();
  }

  /**
   * Positions the specified frame at a relative position in the screen, where
   * 50% is considered to be the center of the screen.
   *
   * @param frame
   *          the frame.
   * @param horizontalPercent
   *          the relative horizontal position of the frame (0.0 to 1.0, where
   *          0.5 is the center of the screen).
   * @param verticalPercent
   *          the relative vertical position of the frame (0.0 to 1.0, where 0.5
   *          is the center of the screen).
   */
  public static void setPositionFrameOnScreen(final Window frame, final double horizontalPercent, final double verticalPercent) {
    final Rectangle r = getMaximumWindowBounds();
    final Dimension f = frame.getSize();
    final int w = Math.max(r.width - f.width, 0);
    final int h = Math.max(r.height - f.height, 0);
    final int x = (int) (horizontalPercent * w) + r.x;
    final int y = (int) (verticalPercent * h) + r.y;
    frame.setBounds(x, y, f.width, f.height);
  }

  /**
   * Creates the ok button.
   *
   * @return the button
   */
  public static JButton createOkButton() {
    JButton btOk = new JButton();
    setLocalizedText(btOk, JNPadBundle.getString("button.ok")); //$NON-NLS-1$
    btOk.setIcon(GUIUtilities.loadIcon("ok.png")); //$NON-NLS-1$
    return btOk;
  }

  /**
   * Creates the cancel button.
   *
   * @return the button
   */
  public static JButton createCancelButton() {
    JButton btCancel = new JButton();
    setLocalizedText(btCancel, JNPadBundle.getString("button.cancel")); //$NON-NLS-1$
    btCancel.setIcon(GUIUtilities.loadIcon("cancel.png")); //$NON-NLS-1$
    return btCancel;
  }

  /**
   * Gets the size.
   *
   * @param size the size
   * @param max the max
   * @return the size
   */
  public static Dimension getSize(Dimension size, Dimension max) {
    if (size.width > max.width)
      size.width = max.width;
    if (size.height > max.height)
      size.height = max.height;
    return size;
  }

  /**
   * Gets the combo box list.
   *
   * @param cmb the combo box
   * @return the combo box list
   */
  @SuppressWarnings("unchecked")
  public static List getComboBoxList(JComboBox cmb) {
    List list = new ArrayList(cmb.getItemCount());
    for (int i = 0; i < cmb.getItemCount(); i++) {
      Object obj = cmb.getItemAt(i);
      if (!list.contains(obj)) {
        list.add(obj);
      }
    }
    if (list.size() > MAX_ITEM_LIST_ELEM)
      Utilities.restrictListSize(list, MAX_ITEM_LIST_ELEM);
    return list;
  }

  /**
   * Sets the combo box list.
   *
   * @param cmb the combo box
   * @param list the list
   */
  public static void setComboBoxList(JComboBox cmb, List<?> list) {
    if (list.size() > MAX_ITEM_LIST_ELEM)
      Utilities.restrictListSize(list, MAX_ITEM_LIST_ELEM);
    for (Object aList : list)
      cmb.addItem(aList);
  }

  /**
   * Gets the item.
   *
   * @param cmb the combo box
   * @return the item
   */
  public static String getItem(JComboBox cmb) {
    return cmb.getEditor().getItem().toString();
  }

  /**
   * Update combo box list.
   *
   * @param cmb the combo box
   */
  public static void updateComboBoxList(JComboBox cmb) {
    updateComboBoxList(cmb, getItem(cmb), MAX_ITEM_LIST_ELEM);
  }

  /**
   * Update combo box list.
   *
   * @param cmb the combo box
   * @param size the size
   */
  public static void updateComboBoxList(JComboBox cmb, int size) {
    updateComboBoxList(cmb, getItem(cmb), size);
  }

  /**
   * Update combo box list.
   *
   * @param cmb the combo box
   * @param item the item
   */
  public static void updateComboBoxList(JComboBox cmb, Object item) {
    updateComboBoxList(cmb, item, MAX_ITEM_LIST_ELEM);
  }

  /**
   * Update combo box list.
   *
   * @param cmb the combo box
   * @param item the item
   * @param size the size
   */
  public static void updateComboBoxList(JComboBox cmb, Object item, int size) {
    if (size < MIN_ITEM_LIST_ELEM)
      size = MIN_ITEM_LIST_ELEM;

    boolean b = true;

    for (int j = 0; j < cmb.getItemCount(); j++) {
      Object item_ = cmb.getItemAt(j++);
      if (item_.equals(item)) {
        cmb.removeItem(item_);
        cmb.insertItemAt(item_, 0);
        cmb.setSelectedItem(item_);
        j = cmb.getItemCount();
        b = false;
      }
    }

    if (b) {
      cmb.insertItemAt(item, 0);
      cmb.setSelectedIndex(0);
      for (; cmb.getItemCount() > size; cmb.removeItemAt(cmb.getItemCount() - 1));
    }
  }
  
  /**
   * Draw border.
   *
   * @param g Graphics
   * @param c Color
   * @param x int
   * @param y int
   * @param w int
   * @param h int
   */
  public static void drawBorder(Graphics g, Color c, int x, int y, int w, int h) {
    g.setColor(c);
    g.drawRect(x, y, w - 1, h - 1);
  }

  /**
   * Draw3 d border.
   *
   * @param g Graphics
   * @param c1 Color
   * @param c2 Color
   * @param x int
   * @param y int
   * @param w int
   * @param h int
   */
  public static void draw3DBorder(Graphics g, Color c1, Color c2, int x, int y, int w, int h) {
    int x2 = x + w - 1;
    int y2 = y + h - 1;
    g.setColor(c1);
    g.drawLine(x, y, x2 - 1, y);
    g.drawLine(x, y + 1, x, y2);
    g.setColor(c2);
    g.drawLine(x, y2, x2 - 1, y2);
    g.drawLine(x2, y, x2, y2);
  }

  /**
   * Draw dotted rectangle.
   *
   * @param g Graphics
   * @param r Rectangle
   * @since 0.3
   */
  public static void drawDottedRectangle(Graphics g, Rectangle r) {
    drawDottedRectangle(g, r.x, r.y, r.x + r.width, r.y + r.height);
  }

  /**
   * Draw dotted rectangle.
   *
   * @param g Graphics
   * @param x top left X coordinate.
   * @param y top left Y coordinate.
   * @param x1 right bottom X coordinate.
   * @param y1 right bottom Y coordinate.
   * @since 0.3
   */
  public static void drawDottedRectangle(Graphics g, int x, int y, int x1, int y1) {
    int i1;
    for (i1 = x; i1 <= x1; i1 += 2) {
      g.drawLine(i1, y, i1, y);
    }
    for (i1 = i1 != x1 + 1 ? y + 2 : y + 1; i1 <= y1; i1 += 2) {
      g.drawLine(x1, i1, x1, i1);
    }
    for (i1 = i1 != y1 + 1 ? x1 - 2 : x1 - 1; i1 >= x; i1 -= 2) {
      g.drawLine(i1, y1, i1, y1);
    }
    for (i1 = i1 != x - 1 ? y1 - 2 : y1 - 1; i1 >= y; i1 -= 2) {
      g.drawLine(x, i1, x, i1);
    }
  }

  /**
   * Paint cross.
   *
   * @param g Graphics
   * @param color Color
   * @param centerX int
   * @param centerY int
   * @param size int
   * @param thickness int
   */
  public static void paintCross(Graphics g, Color color, int centerX, int centerY, int size, int thickness) {
    g.setColor(color);
    size = size / 2;
    for (int i = 0; i < thickness; i++) {
      g.drawLine(centerX - size, centerY - size, centerX + size, centerY + size);
      g.drawLine(centerX + size, centerY - size, centerX - size, centerY + size);
      centerX++;
    }
  }

  /**
   * Paint maximize.
   *
   * @param g Graphics
   * @param color Color
   * @param x int
   * @param y int
   * @param width int
   * @param height int
   * @param height2 int
   * @param thickness int
   */
  public static void paintMaximize(Graphics g, Color color, int x, int y, int width, int height, int height2, int thickness) {
    g.setColor(color);
    int y2 = y + height2;
    for (int i = 0; i < thickness; i++) {
      g.drawRect(x - i, y - i, width + 2 * i, height + 2 * i);
      g.drawLine(x, y2 + i, width + x, y2 + i);
    }
  }

  /**
   * Paint minimize.
   *
   * @param g Graphics
   * @param color Color
   * @param x int
   * @param y int
   * @param width int
   * @param height int
   * @param thickness int
   */
  public static void paintMinimize(Graphics g, Color color, int x, int y, int width, int height, int thickness) {
    g.setColor(color);
    for (int i = 0; i < thickness; i++) {
      g.drawRect(x - i, y - i, width + 2 * i, height + 2 * i);
    }
  }

  /**
   * Paint restore.
   *
   * @param g Graphics
   * @param color Color
   * @param x int
   * @param y int
   * @param width int
   * @param height int
   * @param thickness int
   */
  public static void paintRestore(Graphics g, Color color, int x, int y, int width, int height, int thickness) {
    int x2 = x + width / 2;
    int y2 = y - height / 2;
    g.setColor(color);
    for (int i = 0; i < thickness; i++) {
      g.drawRect(x - i, y - i, width + 2 * i, height + 2 * i);
      g.drawRect(x2 - i, y2 - i, width + 2 * i, height + 2 * i);
    }
  }

  /**
   * Gets the gradient paint.
   *
   * @param color1 Color
   * @param color2 Color
   * @param orientation int
   * @param d Dimension
   * @return GradientPaint
   */
  public static GradientPaint getGradientPaint(Color color1, Color color2, int orientation, Dimension d) {
    return getGradientPaint(color1, color2, orientation, 0, 0, d.width, d.height);
  }

  /**
   * Gets the gradient paint.
   *
   * @param color1 Color
   * @param color2 Color
   * @param orientation int
   * @param x int
   * @param y int
   * @param width int
   * @param height int
   * @return GradientPaint
   */
  public static GradientPaint getGradientPaint(Color color1, Color color2, int orientation, int x, int y, int width, int height) {
    if (m_gpCache == null) {
      m_gpCache = new HashMap<Integer, GradientPaint>(20);
    }
    long l = Double.doubleToLongBits(x) + Double.doubleToLongBits(y) * 37L + Double.doubleToLongBits(width) * 43L + Double.doubleToLongBits(height) * 47L;
    l = 31 * l + orientation;
    Integer key = ((int) l ^ (int) (l >> 32) ^ color1.hashCode() ^ color2.hashCode() * 17) * 31;
    GradientPaint gp = m_gpCache.get(key);
    if (gp == null) {
      switch (orientation) {
        case Orientable.ORIENTATION_HORIZONTAL:
          gp = new GradientPaint(x, height / 2.0f, color1, width, height / 2.0f, color2, true);
          break;
        case Orientable.ORIENTATION_VERTICAL:
          gp = new GradientPaint(width / 2.0f, y, color1, width / 2.0f, height, color2, true);
          break;
        case Orientable.ORIENTATION_TOP_TO_BOTTOM_CROSS:
          gp = new GradientPaint(x, y, color1, width, height, color2, true);
          break;
        case Orientable.ORIENTATION_BOTTOM_TO_TOP_CROSS:
          gp = new GradientPaint(x, height, color1, width, y, color2, true);
          break;
        case SwingConstants.HORIZONTAL:
          gp = new GradientPaint(x, y, color1, width, y, color2, true);
          break;
        case SwingConstants.VERTICAL:
        default:
          gp = new GradientPaint(x, y, color1, x, height, color2, true);
          break;
      }
      if (m_gpCache.size() > 40) {
        m_gpCache.clear();
      }
      m_gpCache.put(key, gp);
    }

    return gp;
  }

  /**
   * Can set window transparency.
   *
   * @return true, if successful
   */
  public static boolean canSetWindowTransparency() {
    try {
      Class.forName("com.sun.awt.AWTUtilities"); //$NON-NLS-1$
      return true;
    }
    catch (Exception e) {
      return false;
    }
  }

 /**
  * AWTUtilities since 1.6.0_10.
  *  here we trick out to stay 1.6.0 compatible !
  *  Just does nothing with older JREs.
  *
  * @param w  Window
  * @param t  1 for opaque (default) 0: invisible (no sense) 0.9 leads to a little bit transparency.
  */
  public static void setWindowTransparency(Window w, float t) {
    // found on http://weblogs.java.net/blog/joshy/archive/2008/06/java_doodle_fad.html
    // also works on macOSX before 1.6.0_10 !!
    if (w instanceof JFrame) {
      JFrame fr = (JFrame) w;
      fr.getRootPane().putClientProperty("Window.alpha", t); //$NON-NLS-1$
    }

    /// 1.6.0_10  AWTUtilities.setWindowOpacity(w, f);
    try {
      Class<?> class_ = Class.forName("com.sun.awt.AWTUtilities"); //$NON-NLS-1$
      Method m = class_.getMethod("setWindowOpacity", new Class[] { Window.class, Float.TYPE }); // trick: don't use w, that may be a JFrame ! //$NON-NLS-1$
      m.invoke(null, w, t);
    }
    catch (Exception ex) {
      LOGGER.log(Level.WARNING, "Can't set window opacity: " + ex.getMessage(), ex); //$NON-NLS-1$
    }
  }

  /**
   * Checks if is mono space font.
   *
   * @param font the font
   * @return true, if is mono space font
   */
  public static boolean isMonoSpaceFont(Font font) {
    FontRenderContext frc = new FontRenderContext(null,
                                                  RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT == RenderingHints.VALUE_TEXT_ANTIALIAS_ON,
                                                  RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT == RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    Rectangle2D iBounds = font.getStringBounds("i", frc); //$NON-NLS-1$
    Rectangle2D mBounds = font.getStringBounds("m", frc); //$NON-NLS-1$
    return iBounds.getWidth() == mBounds.getWidth();
  }

  /**
   * Gets the mono space fonts.
   *
   * @return List
   */
  public static List<Font> getMonoSpaceFonts() {
    Font fonts[] = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
    List<Font> l_monoFonts = new ArrayList<Font>();
    FontRenderContext frc = new FontRenderContext(null,
                                                  RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT == RenderingHints.VALUE_TEXT_ANTIALIAS_ON,
                                                  RenderingHints.VALUE_FRACTIONALMETRICS_DEFAULT == RenderingHints.VALUE_FRACTIONALMETRICS_ON);
    for (Font font : fonts) {
      Rectangle2D iBounds = font.getStringBounds("i", frc); //$NON-NLS-1$
      Rectangle2D mBounds = font.getStringBounds("m", frc); //$NON-NLS-1$
      if (iBounds.getWidth() == mBounds.getWidth()) {
        l_monoFonts.add(font);
      }
    }
    return l_monoFonts;
  }

  /**
   * Gets the standard font sizes.
   *
   * @return the standard font sizes
   */
  public static String[] getStandardFontSizes() {
    //return STANDARD_FONT_SIZES; // Original
    //return Utilities.clone(STANDARD_FONT_SIZES); // Keep FindBugs happy [v0.1]
    return Utilities.copyOf(STANDARD_FONT_SIZES); // Keep FindBugs happy [v0.2]
  }

  /**
   * Checks if is valid font.
   *
   * @param font the font
   * @return true, if is valid font
   * @since 0.3
   */
  public static boolean isValidFont(Font font) {
    try {
      return font.canDisplay('a') &&
             font.canDisplay('z') &&
             font.canDisplay('A') &&
             font.canDisplay('Z') &&
             font.canDisplay('0') &&
             font.canDisplay('1');
    }
    catch (Exception e) {
      // JRE has problems working with the font. Just skip.
      return false;
    }
  }
  
  /**
   * Gets the font styles.
   *
   * @return the font styles
   */
  public static String[] getFontStyles() {
    //return FONT_STYLES; // Original
    //return Utilities.clone(FONT_STYLES); // Keep FindBugs happy [v0.1]
    return Utilities.copyOf(FONT_STYLES); // Keep FindBugs happy [v0.2]
  }

  /**
   * Gets the available font family names.
   *
   * @return the available font family names
   */
  public static String[] getAvailableFontFamilyNames() {
    String[] names = GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames();
    ArrayList<String> l_names = new ArrayList<String>(names.length);
    for (int i = 0, j; i < names.length; i++) {
      for (j = 0; j < HIDE_FONTS.length; j++) {
        if (names[i].contains(HIDE_FONTS[j])) {
          break;
        }
      }
      if (j == HIDE_FONTS.length) {
        l_names.add(names[i]);
      }
    }
    return l_names.toArray(new String[l_names.size()]);
  }

  /**
   * Apply style.
   *
   * @param componentStyle the component style
   * @param comp the comp
   * @since 0.3
   */
  @SuppressWarnings("nls")
  public static void applyStyle(ComponentStyle componentStyle, Component comp) {
    if (!(comp instanceof JComponent))
      return;

    JComponent c = (JComponent) comp;

    if (LAFUtils.isUnderAquaLookAndFeel()) {
      c.putClientProperty("JComponent.sizeVariant",
                          componentStyle == ComponentStyle.REGULAR ? "regular" : componentStyle == ComponentStyle.SMALL ? "small" : "mini");
    }
    else {
      c.setFont(getFont(componentStyle == ComponentStyle.REGULAR ? FontSize.NORMAL :
                        componentStyle == ComponentStyle.SMALL ? FontSize.SMALL : FontSize.TINY, c.getFont()));
    }
    Container p = c.getParent();
    if (p != null) {
      SwingUtilities.updateComponentTreeUI(p);
    }
  }

  /**
   * Gets the font.
   *
   * @param size the size
   * @param base the base
   * @return the font
   * @since 0.3
   */
  public static Font getFont(FontSize size, Font base) {
    if (base == null)
      base = LAFUtils.getLabelFont();
    float fontSize;
    int defSize = base.getSize();
    switch (size) {
      case HUGE:
        fontSize = Math.min(defSize + 4f, 18f);
        break;
      case LARGE:
        fontSize = Math.min(defSize + 2f, 16f);
        break;
      case SMALL:
        fontSize = Math.max(defSize - 2f, 11f);
        break;
      case TINY:
        fontSize = Math.max(defSize - 4f, 9f);
        break;
      default:
        fontSize = defSize;
        break;
    }
    return base.deriveFont(fontSize);
  }
  
  /**
   * Gets the color.
   *
   * @param colorTone the color tone
   * @param base the color base
   * @return the color
   * @since 0.3
   */
  public static Color getColor(ColorTone colorTone, Color base) {
    if (base == null)
      base = LAFUtils.getLabelForeground();
    switch(colorTone) {
      case BRIGHTER:
        return new Color(Math.min(base.getRed()   + 50, 255), 
                         Math.min(base.getGreen() + 50, 255), 
                         Math.min(base.getBlue()  + 50, 255));
      case DARKER:
        return new Color(Math.max(base.getRed()   - 50, 0), 
                         Math.max(base.getGreen() - 50, 0), 
                         Math.max(base.getBlue()  - 50, 0));
      default:  
        return base;
    }
  }
  
  /**
   * Open browser.
   *
   * @param url the url
   * @throws IOException Signals that an I/O exception has occurred.
   * @throws URISyntaxException the uRI syntax exception
   */
  public static void openBrowser(URL url) throws IOException, URISyntaxException {
    openBrowser(url.toString());
  }
  
  /**
   * Open browser.
   *
   * @param url the url
   * @throws IOException Signals that an I/O exception has occurred.
   * @throws URISyntaxException the uRI syntax exception
   */
  public static void openBrowser(final File url) throws IOException, URISyntaxException {
    openBrowser("file://" + url.getAbsolutePath()); //$NON-NLS-1$
  }  

  /**
   * Open browser.
   *
   * @param url the url
   * @throws IOException Signals that an I/O exception has occurred.
   * @throws URISyntaxException the uRI syntax exception
   */
  @SuppressWarnings("nls")
  public static void openBrowser(String url) throws IOException, URISyntaxException {
    /*if (!url.contains("://")) {
      url = "file://" + url;
    }*/

    if (Desktop.isDesktopSupported()) {
      Desktop.getDesktop().browse(new URI(url.replace('\\', '/')));
      return;
    }

    if (Platform.isWindows) {
      Runtime.getRuntime().exec("rundll32 url.dll,FileProtocolHandler " + url);
    }
    else if (Platform.isMac) {
      Runtime.getRuntime().exec("open " + url);
    }
    else {
      // Do a best guess on unix until we get a platform independent way
      // Build a list of browsers to try, in this order.
      String[] browsers = { "epiphany", "firefox", "mozilla", "konqueror",
                            "netscape", "opera", "links", "lynx" };

      // Build a command string which looks like "browser1 "url" || browser2 "url" ||..."
      StringBuilder cmd = new StringBuilder();
      for (int i = 0; i < browsers.length; i++)
        cmd.append(i == 0 ? "" : " || ").append(browsers[i]).append(" \"").append(url).append("\" ");

      Runtime.getRuntime().exec(new String[] { "sh", "-c", cmd.toString() });
    }
  }

  /**
   * Sets the rendering hints.
   *
   * @param g the new rendering hints
   */
  public static void setRenderingHints(Graphics g) {
    if (Config.JNPAD_ANTIALIAS.getValue()) {
      Graphics2D g2d = (Graphics2D) g;
      g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                           RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB); //RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    }
  }

  //////////////////////////////////////////////////////////////////////////////
  /**
   * The Class StringConversion.
   *
   * @version 0.3
   * @since   jNPad v0.1
   */
  public static class StringConversion implements java.io.Serializable {
    private String            _text;
    private LineSeparator     _lineSeparator;
    private String            _result;
    private boolean           _removeEndSpaces;
    private int               _lineCount;
    private int               _tabSize;

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

    /**
     * Instantiates a new string conversion.
     *
     * @param text the text
     * @param removeEndSpaces the remove end spaces
     */
    public StringConversion(String text, boolean removeEndSpaces) {
      this(text, removeEndSpaces, !Config.TEXT_TAB_ENABLED.getValue() ? Config.TEXT_TAB_SIZE.getValue() : -1);
    }

    /**
     * Instantiates a new string conversion.
     *
     * @param text the text
     * @param removeEndSpaces the remove end spaces
     * @param tabSize the tab size
     */
    public StringConversion(String text, boolean removeEndSpaces, int tabSize) {
      _text            = text;
      _removeEndSpaces = removeEndSpaces;
      _tabSize         = tabSize;
    }

    /**
     * Convert.
     */
    public void convert() {
      detectEOL();
      convertText();
    }

    /**
     * Detect eol.
     */
    public void detectEOL() {
      /* Number of characters in 'buf' array.
       InputStream.read() doesn't always fill the
       array (eg, the file size is not a multiple of
       IOBUFSIZE, or it is a GZipped file, etc) */
      int len = _text.length();

      // True if a \n was read after a \r. Usually
      // means this is a DOS/Windows file
      boolean CRLF = false;

      // A \r was read, hence a MacOS file
      boolean CROnly = false;

      // Was the previous read character a \r?
      // If we read a \n and this is true, we assume
      // we have a DOS/Windows file
      boolean lastWasCR = false;

      // Number of lines read. Every 100 lines, we update the
      // progress bar
      _lineCount = 0;

      // Offset of previous line, relative to
      // the start of the I/O buffer (NOT
      // relative to the start of the document)
      @SuppressWarnings("unused")
      int lastLine = 0;

      for (int i = 0; i < len; i++) {
        // Look for line endings.
        switch (_text.charAt(i)) {
          case '\r':

            // If we read a \r and
            // lastWasCR is also true,
            // it is probably a Mac file
            // (\r\r in stream)
            if (lastWasCR) {
              CROnly = true;
              CRLF = false;
            }
            // Otherwise set a flag,
            // so that \n knows that last
            // was a \r
            else {
              lastWasCR = true;
            }
            _lineCount++;
            // This is i+1 to take the
            // trailing \n into account
            lastLine = i + 1;
            break;
          case Utilities.LF:

            /* If lastWasCR is true, we just read a \r followed
             by a \n. We specify that this is a Windows file,
             but take no further action and just ignore the \r. */
            if (lastWasCR) {
              CROnly = false;
              CRLF = true;
              lastWasCR = false;
              /* Bump lastLine so that the next line doesn't erronously
                pick up the \r */
              lastLine = i + 1;
            }
            /* Otherwise, we found a \n that follows some other
             *  character, hence we have a Unix file */
            else {
              CROnly = false;
              CRLF = false;
              _lineCount++;
              lastLine = i + 1;
            }
            break;
          default:
            /*  If we find some other character that follows
             a \r, so it is not a Windows file, and probably
             a Mac file */
            if (lastWasCR) {
              CROnly = true;
              CRLF = false;
              lastWasCR = false;
            }
            break;
        }
      }

      if (_lineCount != 0)
        _lineCount++;

      if (_lineCount == 0) {
        _lineSeparator = LineSeparator.getDefault();
      }
      else if (CRLF) {
        _lineSeparator = LineSeparator.DOS;
      }
      else if (CROnly) {
        _lineSeparator = LineSeparator.MAC;
      }
      else {
        _lineSeparator = LineSeparator.UNIX;
      }
    }

    /**
     * Convert text.
     */
    public void convertText() {
      StringBuilder sb = new StringBuilder(_text.length());

      char[] tab = Utilities.spaces(_tabSize).toCharArray();

      int j = 0;
      char c = '\uFFFF';

      try {
        do {
          if (j >= _text.length())
            break;

          int k = c;
          if (k != 65535)
            c = '\uFFFF';
          else
            k = _text.charAt(j++);

          if ((k == 10 || k == 13) && _removeEndSpaces) { // '\n' o '\r'
            for (int l = sb.length() - 1; l >= 0 && sb.charAt(l) == Utilities.SPACE; sb.deleteCharAt(l--));
          }

          switch (k) {
            case 13: // 13 = CR  (carriage return) -> '\r'
              if(j < _text.length()) {
                c = _text.charAt(j++);
                if (c == Utilities.LF)
                  c = '\uFFFF';
              }
              //$FALL-THROUGH$

            case 10: // 10 = LF  (NL line feed, new line) -> '\n'
              sb.append(Utilities.LF);
              break;

            case 9: // 9 = TAB (horizontal tab) -> '\t'
              if(_tabSize > 0) {
                sb.append(tab);
              }
              else {
                sb.append(Utilities.TAB);
              }
              break;

            case 11: // 11 = VT  (vertical tab) -> '\013'
            case 12: // 12 = FF  (NP form feed, new page) -> '\f'
            default:
              sb.append( (char) k);
              break;
          }
        }
        while (true);

        if (_removeEndSpaces) {
          for (int i = sb.length() - 1; i >= 0 && sb.charAt(i) == Utilities.SPACE; sb.deleteCharAt(i--));
        }
      }
      catch (Exception ex) {
        //ignored
      }

      _result = sb.toString();
    }

    /**
     * Gets the line separator.
     *
     * @return the line separator
     */
    public LineSeparator getLineSeparator() {
      return _lineSeparator;
    }

    /**
     * Gets the result.
     *
     * @return the result
     */
    public String getResult() {
      return _result;
    }

    /**
     * Gets the text.
     *
     * @return the text
     */
    public String getText() {
      return _text;
    }

    /**
     * Checks if is removes the end spaces enabled.
     *
     * @return true, if is removes the end spaces enabled
     */
    public boolean isRemoveEndSpacesEnabled() {
      return _removeEndSpaces;
    }

    /**
     * Sets the removes the end spaces enabled.
     *
     * @param b the new removes the end spaces enabled
     */
    public void setRemoveEndSpacesEnabled(boolean b) {
      _removeEndSpaces = b;
    }

    /**
     * Gets the line count.
     *
     * @return the line count
     */
    public int getLineCount() {
      return _lineCount;
    }

    /**
     * To string.
     *
     * @return the string
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
      return _result;
    }
  }
  //////////////////////////////////////////////////////////////////////////////

}
